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 中已经有数据了,是时候返回给 www.devze.comInputReader 进行处理了。
- 返回要处理事件的数量给 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)); }
设备的扫描过程如下
从驱动中输入设备厂www.devze.com商信息,并保存到 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: /s编程ystem/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.idc 或 XXXTouchScreen.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 (no编程w >= 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 informatio编程客栈n 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开发者_JS开发 = 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处理合成事件的资料请关注我们其它相关文章!
精彩评论