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

目录
  • 正文
  • 生成合成事件
    • 加载并解析输入设备的配置
  • InputReader 处理合成事件
    • 创建与配置 InputDevice
      • 配置基本参数
      • 配置坐标系
      • 配置 Surface
  • 小结

正文

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 /*usingSlotsProtocol*/);
    } else {
        mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS,
                                               false /*usingSlotsProtocol*/);
    }
}

InputMapper 配置坐标系的过程如下

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

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

// TouchInputMapper.h
/* Raw axis information from the driver. */
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
/* Describes an absolute axis. */
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处理合成事件的资料请关注我们其它相关文章!

(0)

相关推荐

  • Input系统之InputReader概要性实例分析

    目录 InputReader 的创建 EventHub 创建过程如下 InputReader 的运行 EventHub 提供事件 InputReader 的创建 从 InputManagerService: 创建与启动 可知,Input 系统的主要功能,主要集中在 native 层,并且Input 系统的 native 层又包含 InputReader, InputClassifer, InputDispatcher 三个子模块.本文来分析 InputReader 从创建到启动的基本流程,为后续

  • Input系统之InputReader处理按键事件详解

    目录 前言 认识按键事件 处理按键事件 扫描码映射按键码 结束 前言 前面几篇文章已经为 Input 系统的分析打好了基础,现在是时候进行更深入的分析了. 通常,手机是不带键盘的,但是手机上仍然有按键,就是我们经常使用的电源键以及音量键.因此还是有必要分析按键事件的处理流程. 那么,掌握按键事件的处理流程,对我们有什么用处呢?例如,手机上添加了一个功能按键,你知道如何把这个物理按键映射到上层,然后处理这个按键吗?又例如,如果设备是不需要电源键,但是系统默认把某一个按键映射为电源键,那么我们如何使

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

    目录 正文 1. InputMapper 处理触摸事件 2. 收集触摸事件信息 3. 处理同步事件 3.1 同步数据 3.2 处理同步后的数据 3.2.1 加工数据 3.2.2 分发事件 结束 正文 手机一般有两种类型的输入设备.一种是键盘类型的输入设备,通常它包含电源键和音量下键.另一种是触摸类型的输入设备,触摸屏就属于这种类型. 键盘类型的输入设备一般都是产生按键事件,前面已经用几篇文章,分析了按键事件的分发流程. 触摸类型的输入设备一般都是产生触摸事件,本文就开始分析触摸事件的分发流程.

  • React合成事件详解

    react合成事件指的是react用js模拟了一个Dom事件流.(fiber树模拟Dom树结构) 合成事件的事件流在fiber树中发生捕获和冒泡. 从点击输入框开始 当你点击input输入框,react在根节点(注1)监听到focus事件(注2)(注3). 如何从原生事件找到对应的虚拟Dom? 此时,react得到的信息只有原生事件对象(nativeEvent).react通过nativeEvent对应的Dom(eventTarget),沿着Dom树向上找到距离该eventTarget最近的被r

  • vue 监听键盘回车事件详解 @keyup.enter || @keyup.enter.native

    vue运行为v-on在监听键盘事件时,添加了特殊的键盘修饰符: <input v-on:keyup.13="submit"> vue还非常贴心地给出了常用按键的别名,这样就不必去记keyCode ~ ~ 上面代码,还可以在这样写: <input v-on:keyup.enter="submit"> <input @keyup.enter="submit"> 全部的键盘别名: .enter .tab .delet

  • javascript数据代理与事件详解分析

    目录 数据代理与事件 回顾Object.defineProperty方法 何为数据代理 Vue中的数据代理 事件的基本使用 事件的修饰符 键盘事件 数据代理与事件 星光不负赶路人,满身花香蝶自来 回顾Object.defineProperty方法 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>回顾Object.defineproperty方法<

  • WPF中鼠标/键盘/拖拽事件以及用行为封装事件详解

    目录 鼠标事件 键盘输入事件 拖拽事件 用行为封装事件 用事件来实现 用行为来封装 本文主要介绍了WPF中常用的鼠标事件.键盘事件以及注意事项,同时使用一个案例讲解了拓展事件.除此之外,本文还讲述如何用行为(Behavior)来封装事件. Windows中的事件通过消息机制来完成,也就是Windows系统来捕获用户输入(如鼠标点击.键盘输入),然后Windows发送一个消息给应用程序,应用程序进行具体的处理.在Winform中,窗体中每个控件都是有独立的句柄,也就是每个控件都可以收到Window

  • Vue自定义事件(详解)

    前面的话 父组件使用props传递数据给子组件,子组件怎么跟父组件通信呢?这时,Vue的自定义事件就派上用场了.本文将详细介绍Vue自定义事件 事件绑定 每个 Vue 实例都实现了事件接口 (Events interface),即 使用 $on(eventName) 监听事件 使用 $emit(eventName) 触发事件 [注意]Vue 的事件系统分离自浏览器的EventTarget API.尽管它们的运行类似,但是 $on 和 $emit 不是addEventListener 和 disp

  • Android 广播大全 Intent Action 事件详解

    具体内容如下所示: Intent.ACTION_AIRPLANE_MODE_CHANGED; //关闭或打开飞行模式时的广播 Intent.ACTION_BATTERY_CHANGED; //充电状态,或者电池的电量发生变化 //电池的充电状态.电荷级别改变,不能通过组建声明接收这个广播,只有通过Context.registerReceiver()注册 Intent.ACTION_BATTERY_LOW; //表示电池电量低 Intent.ACTION_BATTERY_OKAY; //表示电池电

  • 基于BootStrap Metronic开发框架经验小结【五】Bootstrap File Input文件上传插件的用法详解

    Bootstrap文件上传插件File Input是一个不错的文件上传控件,但是搜索使用到的案例不多,使用的时候,也是一步一个脚印一样摸着石头过河,这个控件在界面呈现上,叫我之前使用过的Uploadify 好看一些,功能也强大些,本文主要基于我自己的框架代码案例,介绍其中文件上传插件File Input的使用. 1.文件上传插件File Input介绍 这个插件主页地址是:http://plugins.krajee.com/file-input,可以从这里看到很多Demo的代码展示:http:/

随机推荐