Android Q 输入事件关联显示设备

输入事件关联显示设备

inputevent 的 displayId 是怎么来的?此文章就是介绍 displayid 与 Inputevent 的关系。display 是由 DisplayManagerService 管理的,我们先从 DMS 开始。

显示管理服务(DisplayManagerService)

  1. 有屏幕添加时 DMS 会发送一个 MSG_REQUEST_TRAVERSAL 的 Handle 消息。
    该消息意在通过 DMS 里持有的 WMS 的本地服务去调用 WMS 的 requestTraversal() 。

  2. WMS 窗口遍历的时候会去调用 DMS 的 performTraversalInternal 方法,由 WMS里的 applySurfaceChangesTransaction()方法调用。

  3. DMS 会去配置每一块 device 的信息,其中就包括 viewport。详见 configureDisplayLocked() 、 populateViewportLocked()。

 // Update the corresponding viewport.
if ((info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
    viewportType = VIEWPORT_INTERNAL;
} else if (info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) {
    viewportType = VIEWPORT_EXTERNAL;
} else if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL
        && !TextUtils.isEmpty(info.uniqueId)) {
    viewportType = VIEWPORT_VIRTUAL;
} else {
    Slog.i(TAG, "Display " + info + " does not support input device matching.");
    return;
}

//这里的 ViewPort 里有 device 的 uniqueId,用于 event 匹配 displayID。
private void populateViewportLocked(int viewportType, int displayId, DisplayDevice device, String uniqueId) {
        final DisplayViewport viewport = getViewportLocked(viewportType, uniqueId);
        device.populateViewportLocked(viewport);
        viewport.valid = true;
        viewport.displayId = displayId;//更新Displayid.
    }
    
public final void populateViewportLocked(DisplayViewport viewport) {
        viewport.orientation = mCurrentOrientation;

        if (mCurrentLayerStackRect != null) {
            viewport.logicalFrame.set(mCurrentLayerStackRect);
        } else {
            viewport.logicalFrame.setEmpty();
        }

        if (mCurrentDisplayRect != null) {
            viewport.physicalFrame.set(mCurrentDisplayRect);
        } else {
            viewport.physicalFrame.setEmpty();
        }

        boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90
                || mCurrentOrientation == Surface.ROTATION_270);
        DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
        viewport.deviceWidth = isRotated ? info.height : info.width;
        viewport.deviceHeight = isRotated ? info.width : info.height;

        viewport.uniqueId = info.uniqueId;

        if (info.address instanceof DisplayAddress.Physical) {
            viewport.physicalPort = ((DisplayAddress.Physical) info.address).getPort();
        } else {
            viewport.physicalPort = null;
        }
    }
  1. DMS 会发送一条 MSG_UPDATE_VIEWPORT 消息去设置 viewport,详见 performTraversalLocked()。

  2. IMS 的 本地服务 就会将这些 viewport 信息传递到 JNI 层。

  3. 最终 native 层会去刷新配置将 viewport 信息保存到全局 InputReaderConfiguration 中,详见requestRefreshConfiguration()。

初始化时配置文件的解析

该配置文件是放置在 idc 下,以设备名称命名的 idc 文件。

官方传送门

  1. 解析配置文件时机。
// frameworks/native/services/inputflinger/EventHub.cpp
status_t EventHub::openDeviceLocked(const char* devicePath) {

    ... ...
    Device* device = new Device(fd, deviceId, devicePath, identifier);
    // Load the configuration file for the device.
    loadConfigurationLocked(device);
    
    // Determine whether the device is external or internal.
    if (isExternalDeviceLocked(device)) {
        device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
    }
    ... ...
}
  1. 具体解析配置文件的功能函数
void EventHub::loadConfigurationLocked(Device* device) {
    // 查找文件
    device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
            device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
    if (device->configurationFile.empty()) {
        ALOGD("No input device configuration file found for device '%s'.",
                device->identifier.name.c_str());
    } else {
        //找到了解析文件到configuration。
        status_t status = PropertyMap::load(String8(device->configurationFile.c_str()),
                &device->configuration);
        if (status) {
            ALOGE("Error loading input device configuration file for device '%s'.  "
                    "Using default configuration.",
                    device->identifier.name.c_str());
        }
    }
}
  1. 从 odm、vendor、System里去查找文件是否存。
std::string getInputDeviceConfigurationFilePathByDeviceIdentifier(
        const InputDeviceIdentifier& deviceIdentifier,
        InputDeviceConfigurationFileType type) {
    if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
        if (deviceIdentifier.version != 0) {
            // Try vendor product version.
            std::string versionPath = getInputDeviceConfigurationFilePathByName(
                    StringPrintf("Vendor_%04x_Product_%04x_Version_%04x",
                            deviceIdentifier.vendor, deviceIdentifier.product,
                            deviceIdentifier.version),
                    type);
            if (!versionPath.empty()) {
                return versionPath;
            }
        }

        // Try vendor product.
        std::string productPath = getInputDeviceConfigurationFilePathByName(
                StringPrintf("Vendor_%04x_Product_%04x",
                        deviceIdentifier.vendor, deviceIdentifier.product),
                type);
        if (!productPath.empty()) {
            return productPath;
        }
    }

    // Try device name.
    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.getCanonicalName(), type);
}

std::string getInputDeviceConfigurationFilePathByName(
        const std::string& name, InputDeviceConfigurationFileType type) {
    // Search system repository.
    std::string path;

    // Treblized input device config files will be located /odm/usr or /vendor/usr.
    const char *rootsForPartition[] {"/odm", "/vendor", getenv("ANDROID_ROOT")};
    for (size_t i = 0; i < size(rootsForPartition); i++) {
        if (rootsForPartition[i] == nullptr) {
            continue;
        }
        path = rootsForPartition[i];
        path += "/usr/";
        appendInputDeviceConfigurationFileRelativePath(path, name, type);
        if (!access(path.c_str(), R_OK)) {
            return path;
        }
    }

    // Search user repository.
    // TODO Should only look here if not in safe mode.
    path = "";
    char *androidData = getenv("ANDROID_DATA");
    if (androidData != nullptr) {
        path += androidData;
    }
    path += "/system/devices/";
    appendInputDeviceConfigurationFileRelativePath(path, name, type);
    if (!access(path.c_str(), R_OK)) {
        return path;
    }

    // Not found.
    return "";
}

输入端口与显示端口的配置

在刷新配置的同时会去读取手机目录 /etc/input-port-associations.xml里的内容,该文件是配置输入端口与显示端口的关联关系。

代码详见 IMS 的 getInputPortAssociations()。

// Associations between input ports and display ports
// The java method packs the information in the following manner:
// Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
// Received data: ['inputPort1', '1', 'inputPort2', '2']

<ports>
    <port display="0" input="usb-xhci-hcd.0.auto-1.1/input0" />
    <port display="1" input="usb-xhci-hcd.0.auto-1.2/input0" />
</ports>

Google 文档介绍

配置文件解析行为

// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
     ... ...
     outConfig->portAssociations.clear();
     //Java层读取 input-port-associations.xml 文件
    jobjectArray portAssociations = jobjectArray(env->CallStaticObjectMethod(
            gServiceClassInfo.clazz, gServiceClassInfo.getInputPortAssociations));
    if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
        jsize length = env->GetArrayLength(portAssociations);
        for (jsize i = 0; i < length / 2; i++) {
            std::string inputPort = getStringElementFromJavaArray(env, portAssociations, 2 * i);
            std::string displayPortStr =
                    getStringElementFromJavaArray(env, portAssociations, 2 * i + 1);
            uint8_t displayPort;
            // Should already have been validated earlier, but do it here for safety.
            bool success = ParseUint(displayPortStr, &displayPort);
            if (!success) {
                ALOGE("Could not parse entry in port configuration file, received: %s",
                    displayPortStr.c_str());
                continue;
            }
            outConfig->portAssociations.insert({inputPort, displayPort});//存上。
        }
        env->DeleteLocalRef(portAssociations);
    }
    ... ...
}

分配显示端口

void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) {
     ... ...
     if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
             // In most situations, no port will be specified.
            mAssociatedDisplayPort = std::nullopt;
            // Find the display port that corresponds to the current input port.
            const std::string& inputPort = mIdentifier.location;
            if (!inputPort.empty()) {
                const std::unordered_map<std::string, uint8_t>& ports = config->portAssociations;
                const auto& displayPort = ports.find(inputPort);
                if (displayPort != ports.end()) {
                    mAssociatedDisplayPort = std::make_optional(displayPort->second);
                }
            }
        }
     ... ...
}

ViewPort 查找

ViewPort 的查找会从三个方面去查找——显示端口、唯一的显示ID、显示端口类型。当这些都找不到时才会创建一个 displayid 为 -1,viewtype 为 VIEWPORT_INTERNAL 的 newViewport。

/**
 * Determine which DisplayViewport to use.
 * 1. If display port is specified, return the matching viewport. If matching viewport not
 * found, then return.
 * 2. If a device has associated display, get the matching viewport by either unique id or by
 * the display type (internal or external).
 * 3. Otherwise, use a non-display viewport.
 */
std::optional<DisplayViewport> TouchInputMapper::findViewport() {
    if (mParameters.hasAssociatedDisplay) {
        const std::optional<uint8_t> displayPort = mDevice->getAssociatedDisplayPort();
        if (displayPort) {
            // Find the viewport that contains the same port
            std::optional<DisplayViewport> v = mConfig.getDisplayViewportByPort(*displayPort);
            if (!v) {
                ALOGW("Input device %s should be associated with display on port %" PRIu8 ", "
                        "but the corresponding viewport is not found.",
                        getDeviceName().c_str(), *displayPort);
            }
            return v;
        }

        if (!mParameters.uniqueDisplayId.empty()) {
            return mConfig.getDisplayViewportByUniqueId(mParameters.uniqueDisplayId);
        }

        ViewportType viewportTypeToUse;
        if (mParameters.associatedDisplayIsExternal) {
            viewportTypeToUse = ViewportType::VIEWPORT_EXTERNAL;
        } else {
            viewportTypeToUse = ViewportType::VIEWPORT_INTERNAL;
        }

        std::optional<DisplayViewport> viewport =
                mConfig.getDisplayViewportByType(viewportTypeToUse);
        if (!viewport && viewportTypeToUse == ViewportType::VIEWPORT_EXTERNAL) {
            ALOGW("Input device %s should be associated with external display, "
                    "fallback to internal one for the external viewport is not found.",
                        getDeviceName().c_str());
            viewport = mConfig.getDisplayViewportByType(ViewportType::VIEWPORT_INTERNAL);
        }

        return viewport;
    }

    DisplayViewport newViewport;
    // Raw width and height in the natural orientation.
    int32_t rawWidth = mRawPointerAxes.getRawWidth();
    int32_t rawHeight = mRawPointerAxes.getRawHeight();
    newViewport.setNonDisplayViewport(rawWidth, rawHeight);
    return std::make_optional(newViewport);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值