我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Input系统之InputReader处理合成事件详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Input系统之InputReader处理合成事件详解

正文

Input系统: InputReader 概要性分析 把 InputReader 的事件分为了两类,一类是合成事件,例如设备的增、删事件,另一类是元输入事件,也就是操作设备产生的事件,例如手指在触摸屏上滑动。

本文承接前文,以设备的扫描过程为例,分析合成事件的产生与处理过程。虽然设备的扫描过程只会生成部分合成事件,但是只要我们掌握了这个过程,其他的合成事件的生成以及处理也是水到渠成的事。

生成合成事件

EventHub 扫描设备以及生成合成事件的过程如下

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ALOG_ASSERT(bufferSize >= 1);
    std::scoped_lock _l(mLock);
    struct input_event readBuffer[bufferSize];
    RawEvent* event = buffer;
    size_t capacity = bufferSize;
    bool awoken = false;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        // Reopen input devices if needed.
        if (mNeedToReopenDevices) {
            // ...
        }
        // Report any devices that had last been added/removed.
        for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
            // ...
        }
        // 1. 扫描输入设备
        // mNeedToScanDevices 初始化的值为 true
        if (mNeedToScanDevices) {
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }
        // 2. 为扫描后打开的每一个输入设备,填充一个类型为 DEVICE_ADDED 的事件
        // 扫描过程中会把设备保存到 mOpeningDevices 中。
        while (!mOpeningDevices.empty()) {
            std::unique_ptr<Device> device = std::move(*mOpeningDevices.rbegin());
            mOpeningDevices.pop_back();
            event->when = now;
            event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
            event->type = DEVICE_ADDED;
            event += 1;
            // Try to find a matching video device by comparing device names
            for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end();
                 it++) {
                // ...
            }
            // 每次填充完事件,就把设备 Device 保存到 mDevices 中
            auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device));
            if (!inserted) {
                ALOGW("Device id %d exists, replaced.", device->id);
            }
            // 表明你需要发送设备扫描完成事件
            mNeedToSendFinishedDeviceScan = true;
            if (--capacity == 0) {
                break;
            }
        }
        // 3. 填充设备扫描完成事件
        if (mNeedToSendFinishedDeviceScan) {
            mNeedToSendFinishedDeviceScan = false;
            event->when = now;
            event->type = FINISHED_DEVICE_SCAN;
            event += 1;
            if (--capacity == 0) {
                break;
            }
        }
        // Grab the next input event.
        bool deviceChanged = false;
        // 处理 epoll 事件
        while (mPendingEventIndex < mPendingEventCount) {
            // ...
        }
        // 处理设备改变
        // mPendingEventIndex >= mPendingEventCount 表示处理完所有的输入事件后,再处理设备的改变
        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
            // ...
        }
        // 设备发生改变,那么跳过当前循环,在下一个循环的开头处理设备改变
        if (deviceChanged) {
            continue;
        }
        // 4. 如果有事件,或者被唤醒,那么终止循环,接下来 InputReader 会处理事件或者更新配置
        if (event != buffer || awoken) {
            break;
        }
        mPendingEventIndex = 0;
        mLock.unlock(); // release lock before poll
        // 此时没有事件,并且也没有被唤醒,那么超时等待 epoll 事件
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
        mLock.lock(); // reacquire lock after poll
        if (pollResult == 0) {
            // 处理超时...
        }
        if (pollResult < 0) {
            // 处理错误...
        } else {
            // 保存待处理事件的数量
            mPendingEventCount = size_t(pollResult);
        }
    }
    // 5. 返回事件的数量
    return event - buffer;
}

EventHub 扫描设备以及生成合成事件的过程如下

  • 通过 scanDevicesLocked() 扫描输入设备。扫描过程中会把设备保存到 mOpeningDevices 中。
  • 为每一个输入设备,向 InputReader 提供的 buffer 中,填充一个类型为 DEVICE_ADDED 的事件。并且还要注意,EventHub 还会把输入设备 Device 保存到 mDevices 中。
  • 向 InputReader 提供的 buffer 中,填充一个类型为 FINISHED_DEVICE_SCAN 的事件。
  • 现在 InputReader 提供的 buffer 中已经有数据了,是时候返回给 InputReader 进行处理了。
  • 返回要处理事件的数量给 InputReader。

虽然,从这里我们已经可以明确知道生成了什么类型的合成事件,但是我们的目的不止于此,因此我们深入看看 scanDevicesLocked() 是如何完成设备的扫描的

void EventHub::scanDevicesLocked() {
    // 扫描 /dev/input 目录
    status_t result = scanDirLocked(DEVICE_PATH);
    // ...
}
status_t EventHub::scanDirLocked(const std::string& dirname) {
    // 遍历打开目录项
    for (const auto& entry : std::filesystem::directory_iterator(dirname)) {
        openDeviceLocked(entry.path());
    }
    return 0;
}
void EventHub::openDeviceLocked(const std::string& devicePath) {
    for (const auto& [deviceId, device] : mDevices) {
        if (device->path == devicePath) {
            return; // device was already registered
        }
    }
    char buffer[80];
    ALOGV("Opening device: %s", devicePath.c_str());
    // 打开设备文件
    int fd = open(devicePath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
    if (fd < 0) {
        ALOGE("could not open %s, %s\n", devicePath.c_str(), strerror(errno));
        return;
    }
    // 1. 从驱动获取输入设备厂商信息,并填充到 InputDeviceIdentifier
    InputDeviceIdentifier identifier;
    // Get device name.
    if (ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
        ALOGE("Could not get device name for %s: %s", devicePath.c_str(), strerror(errno));
    } else {
        buffer[sizeof(buffer) - 1] = '\0';
        identifier.name = buffer;
    }
    // ...省略其他信息的获取与填充过程...
    // Allocate device.  (The device object takes ownership of the fd at this point.)
    int32_t deviceId = mNextDeviceId++;
    // 2. 创建代表输入设备的 Device ,并填充数据
    std::unique_ptr<Device> device = std::make_unique<Device>(fd, deviceId, devicePath, identifier);
    // 2.1 加载并解析输入设备的配置文件,解析的结果保存到 EventHub::configuration 中
    device->loadConfigurationLocked();
    // ...
    // 2.2 查找输入设备可以报告哪些种类的事件
    // EV_KEY 表示按键事件,键盘类型设备可以报告此类事件
    device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask);
    // EV_ABS 表示绝对坐标事件,触摸类型设备可以报告此类事件
    device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask);
    // ...
    device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask);
    // 2.3 判断输入设备的类型,保存到 Device::classes 中
    if (device->absBitmask.test(ABS_MT_POSITION_X) && device->absBitmask.test(ABS_MT_POSITION_Y)) {
        // 支持多点触摸的TP
        if (device->keyBitmask.test(BTN_TOUCH) || !haveGamepadButtons) {
            device->classes |= (InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT);
        }
    } else if (device->keyBitmask.test(BTN_TOUCH) && device->absBitmask.test(ABS_X) &&
               device->absBitmask.test(ABS_Y)) {
        // 只支持单点触摸的TP
        device->classes |= InputDeviceClass::TOUCH;
    } else if ((device->absBitmask.test(ABS_PRESSURE) || device->keyBitmask.test(BTN_TOUCH)) &&
               !device->absBitmask.test(ABS_X) && !device->absBitmask.test(ABS_Y)) {
        // ...
    }
    // ... 省略其余输入类型的判断 ...
    // 3. epoll 管理打开的输入设备描述符
    if (registerDeviceForEpollLocked(*device) != OK) {
        return;
    }
    // kernel 进行配置
    device->configureFd();
    // 4. 保存正在打开的输入设备
    // 就是简单的保存到 std::vector<std::unique_ptr<Device>> mOpeningDevices
    addDeviceLocked(std::move(device));
}

设备的扫描过程如下

从驱动中输入设备厂商信息,并保存到 InputDeviceIdentifier 中。

创建代表输入设备 Device 对象,并更新输入设备的信息

  • 加载并解析输入设备的配置文件(不包括键盘配置文件),请参考【加载并解析输入设备的配置】。
  • 更新输入设备能报告的事件类型,例如手机上触摸屏能报告 EV_ABS 类型的事件,它是一个绝对坐标事件。
  • 根据设备能报告的事件类型,判断输入设备的类型。例如,设备能报告 EV_ABS 类型事件,那么它肯定是一个触摸设备,类型肯定有 InputDeviceClass::TOUCH。
  • epoll 监听输入设备描述符的事件,其实就是实时监听输入设备的输入事件的到来。
  • 保存正在打开输入设备。其实就是把创建的 Device 对象保存到 mOpeningDevices 中。

EventHub 所创建的输入设备的信息,可以通过 adb shell dumpsys input 查看

可以通过 dumpsys input 查看 EventHub 获取的设备信息

Event Hub State:
  BuiltInKeyboardId: -2
  Devices:
    ...
    3: XXXTouchScreen
      Classes: KEYBOARD | TOUCH | TOUCH_MT
      Path: /dev/input/event2
      Enabled: true
      Descriptor: 4d66f665abaf83d5d35852472ba90bd54ccd79ae
      Location: input/ts
      ControllerNumber: 0
      UniqueId: 
      Identifier: bus=0x001c, vendor=0x0000, product=0x0000, version=0x0000
      KeyLayoutFile: /system/usr/keylayout/Generic.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile: 
      VideoDevice: <none>
    ...
    7: gpio-keys
      Classes: KEYBOARD
      Path: /dev/input/event4
      Enabled: true
      Descriptor: 485d69228e24f5e46da1598745890b214130dbc4
      Location: gpio-keys/input0
      ControllerNumber: 0
      UniqueId: 
      Identifier: bus=0x0019, vendor=0x0001, product=0x0001, version=0x0100
      KeyLayoutFile: /system/usr/keylayout/gpio-keys.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile: 
      VideoDevice: <none>      

有几点要说明下

  • 创建 Device 后,加载的配置文件,对应于这里的 ConfigurationFile。属性 KeyLayoutFile 和 KeyCharacterMapFile 是代表键盘类输入设备的按键布局文件和按键字符映射文件,它们是在按键事件的处理过程中加载的。
  • 输入设备 XXXTouchScreen 的类型有三个 KEYBOARD,TOUCH,TOUCH_MT,从这可以看出,一个输入设备可以报告多种类型的事件。

加载并解析输入设备的配置

void EventHub::Device::loadConfigurationLocked() {
    // EventHub::configurationFile 保存配置文件的路径
    // 注意,第二个参数为 InputDeviceConfigurationFileType::CONFIGURATION,表示加载以 idc 结尾的文件
    configurationFile =
            getInputDeviceConfigurationFilePathByDeviceIdentifier(identifier,
                                 InputDeviceConfigurationFileType::CONFIGURATION);
    if (configurationFile.empty()) {
        ALOGD("No input device configuration file found for device '%s'.", identifier.name.c_str());
    } else {
        // 解析配置文件
        android::base::Result<std::unique_ptr<PropertyMap>> propertyMap =
                PropertyMap::load(configurationFile.c_str());
        if (!propertyMap.ok()) {
            ALOGE("Error loading input device configuration file for device '%s'.  "
                  "Using default configuration.",
                  identifier.name.c_str());
        } else {
            // EventHub::configuration 保存配置文件的数据
            configuration = std::move(*propertyMap);
        }
    }
}

加载并解析输入设备的配置文件的过程如下

  • 获取配置文件并保存到 EventHub::configurationFile
  • 解析并保存数据保存到 EventHub::configuration

下面分析的几个函数,是加载所有配置文件的基本函数

// frameworks/native/libs/input/InputDevice.cpp
// 注意,此时 type 为 InputDeviceConfigurationFileType::CONFIGURATION
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 /product/usr, /system_ext/usr,
    // /odm/usr or /vendor/usr.
    const char* rootsForPartition[]{"/product", "/system_ext", "/odm", "/vendor",
                                    getenv("ANDROID_ROOT")};
    for (size_t i = 0; i < size(rootsForPartition); i++) {
        if (rootsForPartition[i] == nullptr) {
            continue;
        }
        path = rootsForPartition[i];
        path += "/usr/";
        // 1. 依次从各个分区的的子目录 /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/";
    // 2. 从 /data/system/devices/ 下获取配置文件
    appendInputDeviceConfigurationFileRelativePath(path, name, type);
    if (!access(path.c_str(), R_OK)) {
        return path;
    }
    // Not found.
    return "";
}
static const char* CONFIGURATION_FILE_DIR[] = {
        "idc/",
        "keylayout/",
        "keychars/",
};
static const char* CONFIGURATION_FILE_EXTENSION[] = {
        ".idc",
        ".kl",
        ".kcm",
};
static void appendInputDeviceConfigurationFileRelativePath(std::string& path,
        const std::string& name, InputDeviceConfigurationFileType type) {
    path += CONFIGURATION_FILE_DIR[static_cast<int32_t>(type)];
    path += name;
    path += CONFIGURATION_FILE_EXTENSION[static_cast<int32_t>(type)];
}

所有的配置文件的路径有很多,我用两个正则表达式表示

/[product|system_ext|odm|vendor|system]/usr/[idc|keylayout|keychars]/name.[idc|kl|kcm]

/data/system/devices/[idc|keylayout|keychars]/name.[idc|kl|kcm]

idc 全称是 input device configuration,用于定义输入设备的基本配置。

kl 全称是 key layout,用于定义按键布局。

kcm 全称是 key character map,用于定义按键字符映射。

对于文件名 name,有如下几种情况

  • Vendor_XXX_Product_XXX_Version_XXX(Version不为0的情况下)
  • Vendor_XXX_Product_XXX(Version为0的情况下)
  • 修正后的设备名(设备名中除了字母、数字、下划线、破折号,其实字符都替换为下划线),。
  • 对于键盘类型输入设备,按键配置文件的名字还可以为 Generic。

可以通过 dumpsys input 查看输入设备的所有配置文件

Event Hub State:
  BuiltInKeyboardId: -2
  Devices:
    ...
    3: XXXTouchScreen
      Classes: KEYBOARD | TOUCH | TOUCH_MT
      ...
      Identifier: bus=0x001c, vendor=0x0000, product=0x0000, version=0x0000
      KeyLayoutFile: /system/usr/keylayout/Generic.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile: 
    ...
    7: gpio-keys
      Classes: KEYBOARD
      ...
      Identifier: bus=0x0019, vendor=0x0001, product=0x0001, version=0x0100
      KeyLayoutFile: /system/usr/keylayout/gpio-keys.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile:     

我使用的触摸屏是 XXXTouchScreen ,它的配置文件的文件名可以为 Vendor_0000_Product_0000.idcXXXTouchScreen.idc

InputReader 处理合成事件

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    { // acquire lock
        std::scoped_lock _l(mLock);
        oldGeneration = mGeneration;
        timeoutMillis = -1;
        // 处理配置更新
        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            // 更新配置
            refreshConfigurationLocked(changes);
        } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock
    // 1. 从 EventHub 获取事件
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    { // acquire lock
        std::scoped_lock _l(mLock);
        mReaderIsAliveCondition.notify_all();
        // 2. 处理事件
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
        if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            if (now >= mNextTimeout) {
                mNextTimeout = LLONG_MAX;
                timeoutExpiredLocked(now);
            }
        }
        // 3. 处理输入设备改变
        // 3.1 输入设备改变,重新获取输入设备信息
        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            inputDevices = getInputDevicesLocked();
        }
    } // release lock
    // 3.2 通知监听者,输入设备改变了
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }
    // 4. 刷新队列中缓存的事件,分发事件给 InputClassifier。
    mQueuedListener->flush();
}

InputReader 处理合成事件的过程如下

  • 使用 processEventsLocked() 处理合成事件。
  • 如果设备发生改变,更新输入设备信息后,通知监听者。其实就是把输入设备信息更新到上层的 InputManagerService。
  • 刷新队列,分发事件给 InputClassifier。

本文只分析 processEventsLocked() 如何处理合成事件。

EventHub 的设备扫描,生成了两种类型的事件,一种为 EventHubInterface::DEVICE_ADDED, 另一种为 EventHubInterface::FINISHED_DEVICE_SCAN。现在看下这两种事件的处理过程

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            // ... 处理元输入事件
        } else {
            switch (rawEvent->type) {
                case EventHubInterface::DEVICE_ADDED:
                    addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
                case EventHubInterface::FINISHED_DEVICE_SCAN:
                    handleConfigurationChangedLocked(rawEvent->when);
                    break;                    
                // ...
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}

EventHubInterface::FINISHED_DEVICE_SCAN 类型事件的处理比较简单,如下

void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
    // 汇总所有键盘类型输入设备的meta键状态
    // Reset global meta state because it depends on the list of all configured devices.
    updateGlobalMetaStateLocked();
    // 添加一个代表配置改变的事件到队列中
    // Enqueue configuration changed.
    NotifyConfigurationChangedArgs args(mContext.getNextId(), when);
    mQueuedListener->notifyConfigurationChanged(&args);
}

EventHubInterface::DEVICE_ADDED 类型事件的处理才是重点,如下

void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
    if (mDevices.find(eventHubId) != mDevices.end()) {
        ALOGW("Ignoring spurious device added event for eventHubId %d.", eventHubId);
        return;
    }
    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
    // 1. 创建输入设备 InputDevice
    std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
    // 1.2 使用InputReader的配置mConfig,对输入设备进行配置
    device->configure(when, &mConfig, 0);
    device->reset(when);
    // 2. 保存输入设备
    mDevices.emplace(eventHubId, device);
    // InputDevice -> vector<EventHubId>.
    const auto mapIt = mDeviceToEventHubIdsMap.find(device);
    if (mapIt == mDeviceToEventHubIdsMap.end()) {
        std::vector<int32_t> ids = {eventHubId};
        mDeviceToEventHubIdsMap.emplace(device, ids);
    } else {
        mapIt->second.push_back(eventHubId);
    }
    // mGeneration 加 1,表示输入设备改变了
    bumpGenerationLocked();
    // ...
}

InputReader 对 EventHubInterface::DEVICE_ADDED 类型的事件的处理过程如下

  • 创建代表输入设备的 InputDevice,然后使用 InputReader 的配置对它进行配置。
  • 用数据结构保存 InputDevice,最主要是使用 mDevices 保存。

分析到这里,其实已经明白了整个设备扫描的过程。最后,我们重点分析下 EventHub 如何创建输入设备 InputDevice,以及如何配置它。详见【创建与配置 InputDevice

创建与配置 InputDevice

std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
        int32_t eventHubId, const InputDeviceIdentifier& identifier) {
    auto deviceIt = std::find_if(mDevices.begin(), mDevices.end(), [identifier](auto& devicePair) {
        return devicePair.second->getDescriptor().size() && identifier.descriptor.size() &&
                devicePair.second->getDescriptor() == identifier.descriptor;
    });
    std::shared_ptr<InputDevice> device;
    if (deviceIt != mDevices.end()) {
        device = deviceIt->second;
    } else {
        int32_t deviceId = (eventHubId < END_RESERVED_ID) ? eventHubId : nextInputDeviceIdLocked();
        // 创建 InputDevice
        device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
                                               identifier);
    }
    // InputDevice 创建 InputMapper 集合
    device->addEventHubDevice(eventHubId);
    return device;
}
void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) {
    if (mDevices.find(eventHubId) != mDevices.end()) {
        return;
    }
    // 1. 根据输入设备类型,创建 InputMapper 集合
    std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
    Flags<InputDeviceClass> classes = contextPtr->getDeviceClasses();
    std::vector<std::unique_ptr<InputMapper>> mappers;
    // Touchscreens and touchpad devices.
    if (classes.test(InputDeviceClass::TOUCH_MT)) {
        mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
    } else if (classes.test(InputDeviceClass::TOUCH)) {
        mappers.push_back(std::make_unique<SingleTouchInputMapper>(*contextPtr));
    }
    // 省略其实类型的输入设备的 InputMapper 的创建与保存的过程 ...
    // 2. 保存 InputMapper 集合
    // insert the context into the devices set
    mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
    // InputReader 的 mGerneration 加 1,表示设备改变 
    // Must change generation to flag this device as changed
    bumpGeneration();
}

InputReade 创建 InputDevice 的过程如下

  • 创建 InputDevice 实例。
  • 为 InputDevice 创建 InputMapper 集合。

由于一个输入设备可能有多种类型,而每一种类型对应一个 InputMapper,因此 InputDevice 会拥有多个 InputMapper。当一个输入设备上报某一种类型的事件时,InputDevice 会把这个事件交给所有的 InputMapper 处理,而 InputMapper 根据事件的类型就知道是不是要处理这个事件。

InputMapper 的作用是对输入事件进行加工,例如,把触摸屏的坐标转换为显示屏的坐标,然后把加工后的事件放入 QueuedInputListener 的队列中。InputReader 把从 EventHub 获取的所有事件都处理完毕后,会刷新 QueuedInputListener 的队列,也就是把事件发送给 InputClassifier。

EventHub 中使用 Device 代表输入设备,而 InputReader 使用 InputDevice 代表输入设备,它们之间的差别就是这个 InputMapper。也就是说 InputDevice 能处理事件,而 Device 只能保存输入设备信息。

现在看下如何对 InputDevice 进行配置

void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
                            uint32_t changes) {
    mSources = 0;
    mClasses = Flags<InputDeviceClass>(0);
    mControllerNumber = 0;
    for_each_subdevice([this](InputDeviceContext& context) {
        mClasses |= context.getDeviceClasses();
        int32_t controllerNumber = context.getDeviceControllerNumber();
        if (controllerNumber > 0) {
            if (mControllerNumber && mControllerNumber != controllerNumber) {
                ALOGW("InputDevice::configure(): composite device contains multiple unique "
                      "controller numbers");
            }
            mControllerNumber = controllerNumber;
        }
    });
    mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL);
    mHasMic = mClasses.test(InputDeviceClass::MIC);
    // InputDevice 如果没有 InputMapper ,那么会被忽略
    if (!isIgnored()) {
        if (!changes) { // first time only
            mConfiguration.clear();
            // 遍历所有 InuptMapper 的 InputDeviceContext
            for_each_subdevice([this](InputDeviceContext& context) {
                PropertyMap configuration;
                // 从 EventHub 获取配置文件
                context.getConfiguration(&configuration);
                // 保存输入设备所有的配置文件
                mConfiguration.addAll(&configuration);
            });
        }
        if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
            if (!mClasses.test(InputDeviceClass::VIRTUAL)) {
                // 从上层InputManagerService获取按键布局的配置
                std::shared_ptr<KeyCharacterMap> keyboardLayout =
                        mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier);
                bool shouldBumpGeneration = false;
                for_each_subdevice(
                        [&keyboardLayout, &shouldBumpGeneration](InputDeviceContext& context) {
                            if (context.setKeyboardLayoutOverlay(keyboardLayout)) {
                                shouldBumpGeneration = true;
                            }
                        });
                if (shouldBumpGeneration) {
                    bumpGeneration();
                }
            }
        }
        if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) {
            if (!(mClasses.test(InputDeviceClass::VIRTUAL))) {
                // 从上层InputManagerService获取设备别名的配置
                std::string alias = mContext->getPolicy()->getDeviceAlias(mIdentifier);
                if (mAlias != alias) {
                    mAlias = alias;
                    bumpGeneration();
                }
            }
        }
        if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) {
            // 从InputReader的配置mConfig中,检测输入设备是否被拆除在外,并相应设置设备的 enabled 状态
            auto it = config->disabledDevices.find(mId);
            bool enabled = it == config->disabledDevices.end();
            setEnabled(enabled, when);
        }
        if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
            // ...
        }
        // 重点: 使用InputReader的配置mConfig,对InputMapper进行配置
        for_each_mapper([this, when, config, changes](InputMapper& mapper) {
            mapper.configure(when, config, changes);
            mSources |= mapper.getSources();
        });
        // If a device is just plugged but it might be disabled, we need to update some info like
        // axis range of touch from each InputMapper first, then disable it.
        if (!changes) {
            setEnabled(config->disabledDevices.find(mId) == config->disabledDevices.end(), when);
        }
    }
}

对 InputDevice 进行配置的数据源有如下几个

  • EventHub。
  • 上层的 InputManagerService。
  • InputReader 的配置 mConfig。

我们不用纠结配置 InputDevice 的数据是什么,因为可以通过 adb shell dumpsys input 导出 InputDevice 的配置

Input Reader State (Nums of device: 8):
Device 4: XXXTouchScreen
    EventHub Devices: [ 5 ] 
    Generation: 59
    IsExternal: false
    AssociatedDisplayPort: <none>
    AssociatedDisplayUniqueId: <none>
    HasMic:     false
    Sources: 0x00001103
    KeyboardType: 1
    ControllerNum: 0
    ...

在对 InputDevice 的配置过程中,有一个很重要的地方,那就是对 InputMapper 进行配置。

对于触摸屏设备而言,它的所有的 InputMapper 都有一个公共的父类 TouchInputMapper,如果这个触摸屏支持多点触摸,这个 InputMapper 就是 MultiTouchInputMapper,而如果只支持单点触摸,那么这个 InputMapper 就是 SingleTouchInputMapper。

我们以手机的触摸屏为例,它的 InputMapper 的配置过程是在 TouchInputMapper 中完成的

// 注意,参数 config 是 InputReader 的配置 mConfig
void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
                                 uint32_t changes) {
    // 父类实现为空                               
    InputMapper::configure(when, config, changes);
    // TouchInputMapper 还保存了 InputReader 的配置
    mConfig = *config;
    if (!changes) { // first time only
        // Configure basic parameters.
        // 1. 配置基本的参数,参数保存到 TouchInputMapper::mParameters
        configureParameters();
        // Configure common accumulators.
        mCursorScrollAccumulator.configure(getDeviceContext());
        mTouchButtonAccumulator.configure(getDeviceContext());
        // Configure absolute axis information.
        // 2. 配置坐标系
        // 由子类实现
        configureRawPointerAxes();
        // Prepare input device calibration.
        parseCalibration();
        resolveCalibration();
    }
    if (!changes || (changes & InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION)) {
        // Update location calibration to reflect current settings
        updateAffineTransformation();
    }
    if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
        // Update pointer speed.
        mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters);
        mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
        mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
    }
    bool resetNeeded = false;
    if (!changes ||
        (changes &
         (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
          InputReaderConfiguration::CHANGE_POINTER_CAPTURE |
          InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
          InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
          InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) {
        // Configure device sources, surface dimensions, orientation and
        // scaling factors.
        // 3. 配置 surface
        configureSurface(when, &resetNeeded);
    }
    if (changes && resetNeeded) {
        // Send reset, unless this is the first time the device has been configured,
        // in which case the reader will call reset itself after all mappers are ready.
        NotifyDeviceResetArgs args(getContext()->getNextId(), when, getDeviceId());
        getListener()->notifyDeviceReset(&args);
    }
}

对于触摸屏而言,配置 InputMapper 的主要过程如下

  • 通过 configureParameters() 配置基本的参数。什么是基本参数?按我的理解,就是保持不变的参数。这些基本参数会保存到 InputDevice::mParameters 中。详见【配置基本参数
  • 通过 configureRawPointerAxes() 配置坐标系,不过这个函数由子类实现,对于支持多点触摸的输入设备,子类就是 MultiTouchInputMapper。其实就是获取触摸屏的坐标系信息,例如 x, y 方向的坐标点的信息(例如,最大值,最小值)。详见【配置坐标系
  • 通过 configureSurface() 配置 surface 的参数。这些参数决定了如何从触摸屏坐标转换为显示屏坐标。【配置 Surface

配置基本参数

void TouchInputMapper::configureParameters() {
    mParameters.gestureMode = getDeviceContext().hasInputProperty(INPUT_PROP_SEMI_MT)
            ? Parameters::GestureMode::SINGLE_TOUCH
            : Parameters::GestureMode::MULTI_TOUCH;
    String8 gestureModeString;
    if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.gestureMode"),
                                                             gestureModeString)) {
        if (gestureModeString == "single-touch") {
            mParameters.gestureMode = Parameters::GestureMode::SINGLE_TOUCH;
        } else if (gestureModeString == "multi-touch") {
            mParameters.gestureMode = Parameters::GestureMode::MULTI_TOUCH;
        } else if (gestureModeString != "default") {
            ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string());
        }
    }
    // 通过 EventHub 从驱动获取信息,决定设备类型 mParameters.deviceType
    if (getDeviceContext().hasInputProperty(INPUT_PROP_DIRECT)) {
        // The device is a touch screen.
        mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
    } else if (getDeviceContext().hasInputProperty(INPUT_PROP_POINTER)) {
        // The device is a pointing device like a track pad.
        mParameters.deviceType = Parameters::DeviceType::POINTER;
    } else if (getDeviceContext().hasRelativeAxis(REL_X) ||
               getDeviceContext().hasRelativeAxis(REL_Y)) {
        // The device is a cursor device with a touch pad attached.
        // By default don't use the touch pad to move the pointer.
        mParameters.deviceType = Parameters::DeviceType::TOUCH_PAD;
    } else {
        // The device is a touch pad of unknown purpose.
        mParameters.deviceType = Parameters::DeviceType::POINTER;
    }
    mParameters.hasButtonUnderPad = getDeviceContext().hasInputProperty(INPUT_PROP_BUTTONPAD);
    // 根据配置文件,决定设备类型
    String8 deviceTypeString;
    if (getDeviceContext().getConfiguration().tryGetProperty(String8("touch.deviceType"),
                                                             deviceTypeString)) {
        if (deviceTypeString == "touchScreen") {
            mParameters.deviceType = Parameters::DeviceType::TOUCH_SCREEN;
        } else if (deviceTypeString == "touchPad") {
            mParameters.deviceType = Parameters::DeviceType::TOUCH_PAD;
        } else if (deviceTypeString == "touchNavigation") {
            mParameters.deviceType = Parameters::DeviceType::TOUCH_NAVIGATION;
        } else if (deviceTypeString == "pointer") {
            mParameters.deviceType = Parameters::DeviceType::POINTER;
        } else if (deviceTypeString != "default") {
            ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
        }
    }
    // 触摸屏是方向敏感的,因此 mParameters.orientationAware 默认为 true
    mParameters.orientationAware = mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
    // 但是,配置文件可以通过 touch.orientationAware 属性改变 mParameters.orientationAware
    getDeviceContext().getConfiguration().tryGetProperty(String8("touch.orientationAware"),
                                                         mParameters.orientationAware);
    mParameters.hasAssociatedDisplay = false;
    mParameters.associatedDisplayIsExternal = false;
    if (mParameters.orientationAware ||
        mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN ||
        mParameters.deviceType == Parameters::DeviceType::POINTER) {
        // 对于触摸屏设备,mParameters.hasAssociatedDisplay 为 true, 表示必须有关联的显示屏?
        mParameters.hasAssociatedDisplay = true;
        if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) {
            mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal();
            // 配置文件指定输入设备关联的显示屏?
            String8 uniqueDisplayId;
            getDeviceContext().getConfiguration().tryGetProperty(String8("touch.displayId"),
                                                                 uniqueDisplayId);
            mParameters.uniqueDisplayId = uniqueDisplayId.c_str();
        }
    }
    if (getDeviceContext().getAssociatedDisplayPort()) {
        mParameters.hasAssociatedDisplay = true;
    }
    // Initial downs on external touch devices should wake the device.
    // Normally we don't do this for internal touch screens to prevent them from waking
    // up in your pocket but you can enable it using the input device configuration.
    // 这里有一个非常有意思的事情,根据注释所说,外部设备在触摸时会被自动唤醒
    // 而内部设备,需要在配置文件中添加 touch.wake=true,才会在触摸时被唤醒,但是为了防止在口袋中无触摸唤醒设备,一般不配置这个属性
    mParameters.wake = getDeviceContext().isExternal();
    getDeviceContext().getConfiguration().tryGetProperty(String8("touch.wake"), mParameters.wake);
}

基本参数 InputDevice::mParameters 基本是由配置文件决定的,还有一部分是从驱动获取的,这些信息应该都是“死”的。

同样,InputDevice::mParameters 数据也是可以通过 adb shell dumpsys input 查看

  Device 5: NVTCapacitiveTouchScreen
    ....
    Touch Input Mapper (mode - DIRECT):
      Parameters:
        GestureMode: MULTI_TOUCH
        DeviceType: TOUCH_SCREEN
        AssociatedDisplay: hasAssociatedDisplay=true, isExternal=false, displayId=''
        OrientationAware: true

配置坐标系

void MultiTouchInputMapper::configureRawPointerAxes() {
    TouchInputMapper::configureRawPointerAxes();
    // 1. 获取坐标系的信息
    // 最终调用 EventHub::getAbsoluteAxisInfo(), 询问驱动,获取想要的数据
    getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x);
    getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y);
    getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor);
    getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor);
    getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor);
    getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor);
    getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation);
    getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure);
    getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance);
    getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId);
    getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot);
    if (mRawPointerAxes.trackingId.valid && mRawPointerAxes.slot.valid &&
        mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) {
        size_t slotCount = mRawPointerAxes.slot.maxValue + 1;
        if (slotCount > MAX_SLOTS) {
            ALOGW("MultiTouch Device %s reported %zu slots but the framework "
                  "only supports a maximum of %zu slots at this time.",
                  getDeviceName().c_str(), slotCount, MAX_SLOTS);
            slotCount = MAX_SLOTS;
        }
        // 2. 对触摸事件的累加器进行配置
        // 对累加器 MultiTouchMotionAccumulator 进行配置,其实就是分配 Slot 数组
        // 最后一个参数表示是否使用slot协议
        mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount,
                                               true );
    } else {
        mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS,
                                               false );
    }
}

InputMapper 配置坐标系的过程如下

  • 通过 getAbsoluteAxisInfo() 从驱动获取坐标系的信息,然后保存到 MultiTouchInputMapper::mRawPointerAxes
  • 对触摸事件的累加器进行配置

介绍下坐标系的信息结构,mRawPointerAxes 的类型为 RawPointerAxes,代表一个坐标系,如下

// TouchInputMapper.h

struct RawPointerAxes {
    RawAbsoluteAxisInfo x;
    RawAbsoluteAxisInfo y;
    RawAbsoluteAxisInfo pressure;
    RawAbsoluteAxisInfo touchMajor;
    RawAbsoluteAxisInfo touchMinor;
    RawAbsoluteAxisInfo toolMajor;
    RawAbsoluteAxisInfo toolMinor;
    RawAbsoluteAxisInfo orientation;
    RawAbsoluteAxisInfo distance;
    RawAbsoluteAxisInfo tiltX;
    RawAbsoluteAxisInfo tiltY;
    RawAbsoluteAxisInfo trackingId;
    RawAbsoluteAxisInfo slot;
    RawPointerAxes();
    inline int32_t getRawWidth() const { return x.maxValue - x.minValue + 1; }
    inline int32_t getRawHeight() const { return y.maxValue - y.minValue + 1; }
    void clear();
};

每一个成员变量都代表坐标系的某一种信息,但是非常有意思的事情是,所有成员变量的类型都为 RawAbsoluteAxisInfo,如下

// EventHub.h

struct RawAbsoluteAxisInfo {
    bool valid; // true if the information is valid, false otherwise
    int32_t minValue;   // minimum value
    int32_t maxValue;   // maximum value
    int32_t flat;       // center flat position, eg. flat == 8 means center is between -8 and 8
    int32_t fuzz;       // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
    int32_t resolution; // resolution in units per mm or radians per mm
    inline void clear() {
        valid = false;
        minValue = 0;
        maxValue = 0;
        flat = 0;
        fuzz = 0;
        resolution = 0;
    }
};

虽然坐标系的每一种信息都由 RawAbsoluteAxisInfo 表示,但是并不是 RawAbsoluteAxisInfo 所有的成员变量都有效,毕竟信息都有差异的。这里介绍几个与触摸屏相关的成员变量所代表的意思

  • RawAbsoluteAxisInfo:valid 表示坐标系是否支持此信息。
  • RawAbsoluteAxisInfo::minValue 和 RawAbsoluteAxisInfo::maxValue,它们表示 x 轴和 y 轴的坐标范围。

同样地,可以通过 adb shell dumpsys input 把坐标系的信息导出来

  Device 5: NVTCapacitiveTouchScreen
      ...
      Raw Touch Axes:
        X: min=0, max=719, flat=0, fuzz=0, resolution=0
        Y: min=0, max=1599, flat=0, fuzz=0, resolution=0
        Pressure: min=0, max=1000, flat=0, fuzz=0, resolution=0
        TouchMajor: min=0, max=255, flat=0, fuzz=0, resolution=0
        TouchMinor: unknown range
        ToolMajor: unknown range
        ToolMinor: unknown range
        Orientation: unknown range
        Distance: unknown range
        TiltX: unknown range
        TiltY: unknown range
        TrackingId: min=0, max=65535, flat=0, fuzz=0, resolution=0
        Slot: min=0, max=9, flat=0, fuzz=0, resolution=0

现在接着看下如何对触摸事件累加器进行配置

// 第三个参数 usingSlotsProtocol 表示是否使用 slot 协议
void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount,
                                            bool usingSlotsProtocol) {
    mSlotCount = slotCount;
    mUsingSlotsProtocol = usingSlotsProtocol;
    mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
    delete[] mSlots;
    // 原来就是分配 Slot 数组
    mSlots = new Slot[slotCount];
}

触摸事件累加器的配置过程非常简单,就是创建 Slot 数组。当手指在触摸屏上滑动时,Slot 数组中的每一个元素对应一个手指,保存相应手指所产生的一连串的坐标事件。

配置 Surface

void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
    DeviceMode oldDeviceMode = mDeviceMode;
    resolveExternalStylusPresence();
    // Determine device mode.
    if (mParameters.deviceType == Parameters::DeviceType::POINTER &&
        // ...
    } else if (isTouchScreen()) {
        mSource = AINPUT_SOURCE_TOUCHSCREEN;
        mDeviceMode = DeviceMode::DIRECT;
        if (hasStylus()) {
            mSource |= AINPUT_SOURCE_STYLUS;
        }
        if (hasExternalStylus()) {
            mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
        }
    }
    if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
        mDeviceMode = DeviceMode::DISABLED;
        return;
    }.
    std::optional<DisplayViewport> newViewport = findViewport();
    if (!newViewport) {
        mDeviceMode = DeviceMode::DISABLED;
        return;
    }
    if (!newViewport->isActive) {
        mDeviceMode = DeviceMode::DISABLED;
        return;
    }
    // Raw width and height in the natural orientation.
    int32_t rawWidth = mRawPointerAxes.getRawWidth();
    int32_t rawHeight = mRawPointerAxes.getRawHeight();
    bool viewportChanged = mViewport != *newViewport;
    bool skipViewportUpdate = false;
    if (viewportChanged) {
        bool viewportOrientationChanged = mViewport.orientation != newViewport->orientation;
        mViewport = *newViewport;
        if (mDeviceMode == DeviceMode::DIRECT || mDeviceMode == DeviceMode::POINTER) {
            // Convert rotated viewport to natural surface coordinates.
            // 下面是根据屏幕方向,转换坐标
            int32_t naturalLogicalWidth, naturalLogicalHeight;
            int32_t naturalPhysicalWidth, naturalPhysicalHeight;
            int32_t naturalPhysicalLeft, naturalPhysicalTop;
            int32_t naturalDeviceWidth, naturalDeviceHeight;
            switch (mViewport.orientation) {
                case DISPLAY_ORIENTATION_90:
                    naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
                    naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
                    naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
                    naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
                    naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
                    naturalPhysicalTop = mViewport.physicalLeft;
                    naturalDeviceWidth = mViewport.deviceHeight;
                    naturalDeviceHeight = mViewport.deviceWidth;
                    break;
                case DISPLAY_ORIENTATION_180:
                    naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
                    naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
                    naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
                    naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
                    naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
                    naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom;
                    naturalDeviceWidth = mViewport.deviceWidth;
                    naturalDeviceHeight = mViewport.deviceHeight;
                    break;
                case DISPLAY_ORIENTATION_270:
                    naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
                    naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
                    naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
                    naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
                    naturalPhysicalLeft = mViewport.physicalTop;
                    naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight;
                    naturalDeviceWidth = mViewport.deviceHeight;
                    naturalDeviceHeight = mViewport.deviceWidth;
                    break;
                case DISPLAY_ORIENTATION_0:
                default:
                    naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
                    naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
                    naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
                    naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
                    naturalPhysicalLeft = mViewport.physicalLeft;
                    naturalPhysicalTop = mViewport.physicalTop;
                    naturalDeviceWidth = mViewport.deviceWidth;
                    naturalDeviceHeight = mViewport.deviceHeight;
                    break;
            }
            if (naturalPhysicalHeight == 0 || naturalPhysicalWidth == 0) {
                ALOGE("Viewport is not set properly: %s", mViewport.toString().c_str());
                naturalPhysicalHeight = naturalPhysicalHeight == 0 ? 1 : naturalPhysicalHeight;
                naturalPhysicalWidth = naturalPhysicalWidth == 0 ? 1 : naturalPhysicalWidth;
            }
            mPhysicalWidth = naturalPhysicalWidth;
            mPhysicalHeight = naturalPhysicalHeight;
            mPhysicalLeft = naturalPhysicalLeft;
            mPhysicalTop = naturalPhysicalTop;
            const int32_t oldSurfaceWidth = mRawSurfaceWidth;
            const int32_t oldSurfaceHeight = mRawSurfaceHeight;
            mRawSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
            mRawSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
            mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
            mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
            mSurfaceRight = mSurfaceLeft + naturalLogicalWidth;
            mSurfaceBottom = mSurfaceTop + naturalLogicalHeight;
            if (isPerWindowInputRotationEnabled()) {
                // When per-window input rotation is enabled, InputReader works in the un-rotated
                // coordinate space, so we don't need to do anything if the device is already
                // orientation-aware. If the device is not orientation-aware, then we need to apply
                // the inverse rotation of the display so that when the display rotation is applied
                // later as a part of the per-window transform, we get the expected screen
                // coordinates.
                mSurfaceOrientation = mParameters.orientationAware
                        ? DISPLAY_ORIENTATION_0
                        : getInverseRotation(mViewport.orientation);
                // For orientation-aware devices that work in the un-rotated coordinate space, the
                // viewport update should be skipped if it is only a change in the orientation.
                skipViewportUpdate = mParameters.orientationAware &&
                        mRawSurfaceWidth == oldSurfaceWidth &&
                        mRawSurfaceHeight == oldSurfaceHeight && viewportOrientationChanged;
            } else {
                mSurfaceOrientation = mParameters.orientationAware ? mViewport.orientation
                                                                   : DISPLAY_ORIENTATION_0;
            }
        } else {
            // ...
        }
    }
    // If moving between pointer modes, need to reset some state.
    bool deviceModeChanged = mDeviceMode != oldDeviceMode;
    if (deviceModeChanged) {
        mOrientedRanges.clear();
    }
    // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to
    // preserve the cursor position.
    // 这里与开发者模式下的 Touch taps 功能有关,也就是显示触摸点
    if (mDeviceMode == DeviceMode::POINTER ||
        (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
        (mParameters.deviceType == Parameters::DeviceType::POINTER && mConfig.pointerCapture)) {
        if (mPointerController == nullptr) {
            mPointerController = getContext()->getPointerController(getDeviceId());
        }
        if (mConfig.pointerCapture) {
            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
        }
    } else {
        mPointerController.reset();
    }
    if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) {
        ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
              "display id %d",
              getDeviceId(), getDeviceName().c_str(), mRawSurfaceWidth, mRawSurfaceHeight,
              mSurfaceOrientation, mDeviceMode, mViewport.displayId);
        // Configure X and Y factors.
        mXScale = float(mRawSurfaceWidth) / rawWidth;
        mYScale = float(mRawSurfaceHeight) / rawHeight;
        mXTranslate = -mSurfaceLeft;
        mYTranslate = -mSurfaceTop;
        mXPrecision = 1.0f / mXScale;
        mYPrecision = 1.0f / mYScale;
        mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
        mOrientedRanges.x.source = mSource;
        mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
        mOrientedRanges.y.source = mSource;
        configureVirtualKeys();
        // Scale factor for terms that are not oriented in a particular axis.
        // If the pixels are square then xScale == yScale otherwise we fake it
        // by choosing an average.
        mGeometricScale = avg(mXScale, mYScale);
        // Size of diagonal axis.
        float diagonalSize = hypotf(mRawSurfaceWidth, mRawSurfaceHeight);
        // Size factors.
        // ...
        // Pressure factors.
        // ...
        // Tilt
        // ...
        // Orientation
        // ...
        // Distance
        // ...
        // Location
        updateAffineTransformation();
        if (mDeviceMode == DeviceMode::POINTER) {
            // ...
        }
        // Inform the dispatcher about the changes.
        *outResetNeeded = true;
        bumpGeneration();
    }
}

这些信息太复杂了,不过还是可以通过 adb shell dupmsys input 导出来

 Device 5: NVTCapacitiveTouchScreen
    ...
    Touch Input Mapper (mode - DIRECT):
      ....
      Viewport INTERNAL: displayId=0, uniqueId=local:4630946979286703745, port=129, orientation=0, logicalFrame=[0, 0, 720, 1600], physicalFrame=[0, 0, 720, 1600], deviceSize=[720, 1600], isActive=[1]
      RawSurfaceWidth: 720px
      RawSurfaceHeight: 1600px
      SurfaceLeft: 0
      SurfaceTop: 0
      SurfaceRight: 720
      SurfaceBottom: 1600
      PhysicalWidth: 720px
      PhysicalHeight: 1600px
      PhysicalLeft: 0
      PhysicalTop: 0
      SurfaceOrientation: 0
      Translation and Scaling Factors:
        XTranslate: 0.000
        YTranslate: 0.000
        XScale: 1.000
        YScale: 1.000
        XPrecision: 1.000
        YPrecision: 1.000
        GeometricScale: 1.000
        PressureScale: 0.001
        SizeScale: 0.004
        OrientationScale: 0.000
        DistanceScale: 0.000
        HaveTilt: false
        TiltXCenter: 0.000
        TiltXScale: 0.000
        TiltYCenter: 0.000
        TiltYScale: 0.000

小结

设备的扫描与合成事件的处理过程如下

  • EventHub 创建并保存输入设备 Deivce 到 mDevices 中,其中包括为输入设备加载配置文件。
  • InputReader 创建并保存输入设备 InputDevice 到 mDevices 中,其中包括对 InputDevice 进行配置,对 InputDevice 所保存的 InputMapper 进行配置。

设备的扫描与合成事件的处理过程并不复杂,但是对输入设备是极其繁琐的,这些配置数据到底有何用,我们在后面分析输入事件的处理过程中,再见分晓。

以上就是Input系统之InputReader处理合成事件详解的详细内容,更多关于InputReader处理合成事件的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Input系统之InputReader处理合成事件详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Input系统之InputReader处理合成事件详解

这篇文章主要为大家介绍了Input系统之InputReader处理合成事件详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-16

Input系统之InputReader处理触摸事件案例

这篇文章主要为大家介绍了Input系统之InputReader处理触摸事件案例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-14

Vue3学习之事件处理详解

Vue事件处理是每个Vue项目的必要方面。它用于捕获用户输入,共享数据以及许多其他创造性方式。本文将通过简单的示例为大家讲解了Vue3中事件处理的使用,需要的可以参考一下
2022-12-08

Linux文件系统之重定向的实现原理详解

本文详细讲解了Linux文件系统中的重定向实现原理。重定向通过识别重定向符,重定向文件描述符,系统调用和进程执行来实现。重定向输入将标准输入文件描述符复制到指定文件。重定向输出将标准输出文件描述符复制到指定文件。错误输出和管道也是重定向形式。
Linux文件系统之重定向的实现原理详解
2024-04-02

SQLServer 错误 825 在失败 %d 次(错误: %ls)之后,按偏移量 %#016I64x 对文件“%ls”读取成功。 SQL Server 错误日志和系统事件日志中的其他消息中可能有更详

详细信息 Attribute 值 产品名称 SQL Server 事件 ID 825 事件源 MSSQLSERVER 组件 SQLEngine 符号名称 B_RETRYWORKED 消息正文 在失败 ...
SQLServer 错误 825 在失败 %d 次(错误: %ls)之后,按偏移量 %#016I64x 对文件“%ls”读取成功。 SQL Server 错误日志和系统事件日志中的其他消息中可能有更详
2023-11-05

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录