Input系統(tǒng)之InputReader處理合成事件詳解
正文
Input系統(tǒng): InputReader 概要性分析 把 InputReader 的事件分為了兩類,一類是合成事件,例如設(shè)備的增、刪事件,另一類是元輸入事件,也就是操作設(shè)備產(chǎn)生的事件,例如手指在觸摸屏上滑動。
本文承接前文,以設(shè)備的掃描過程為例,分析合成事件的產(chǎn)生與處理過程。雖然設(shè)備的掃描過程只會生成部分合成事件,但是只要我們掌握了這個過程,其他的合成事件的生成以及處理也是水到渠成的事。
生成合成事件
EventHub 掃描設(shè)備以及生成合成事件的過程如下
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. 掃描輸入設(shè)備 // mNeedToScanDevices 初始化的值為 true if (mNeedToScanDevices) { mNeedToScanDevices = false; scanDevicesLocked(); mNeedToSendFinishedDeviceScan = true; } // 2. 為掃描后打開的每一個輸入設(shè)備,填充一個類型為 DEVICE_ADDED 的事件 // 掃描過程中會把設(shè)備保存到 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++) { // ... } // 每次填充完事件,就把設(shè)備 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); } // 表明你需要發(fā)送設(shè)備掃描完成事件 mNeedToSendFinishedDeviceScan = true; if (--capacity == 0) { break; } } // 3. 填充設(shè)備掃描完成事件 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) { // ... } // 處理設(shè)備改變 // mPendingEventIndex >= mPendingEventCount 表示處理完所有的輸入事件后,再處理設(shè)備的改變 if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) { // ... } // 設(shè)備發(fā)生改變,那么跳過當(dāng)前循環(huán),在下一個循環(huán)的開頭處理設(shè)備改變 if (deviceChanged) { continue; } // 4. 如果有事件,或者被喚醒,那么終止循環(huán),接下來 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 { // 保存待處理事件的數(shù)量 mPendingEventCount = size_t(pollResult); } } // 5. 返回事件的數(shù)量 return event - buffer; }
EventHub 掃描設(shè)備以及生成合成事件的過程如下
- 通過 scanDevicesLocked() 掃描輸入設(shè)備。掃描過程中會把設(shè)備保存到 mOpeningDevices 中。
- 為每一個輸入設(shè)備,向 InputReader 提供的 buffer 中,填充一個類型為 DEVICE_ADDED 的事件。并且還要注意,EventHub 還會把輸入設(shè)備 Device 保存到 mDevices 中。
- 向 InputReader 提供的 buffer 中,填充一個類型為 FINISHED_DEVICE_SCAN 的事件。
- 現(xiàn)在 InputReader 提供的 buffer 中已經(jīng)有數(shù)據(jù)了,是時候返回給 InputReader 進(jìn)行處理了。
- 返回要處理事件的數(shù)量給 InputReader。
雖然,從這里我們已經(jīng)可以明確知道生成了什么類型的合成事件,但是我們的目的不止于此,因此我們深入看看 scanDevicesLocked() 是如何完成設(shè)備的掃描的
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()); // 打開設(shè)備文件 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. 從驅(qū)動獲取輸入設(shè)備廠商信息,并填充到 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. 創(chuàng)建代表輸入設(shè)備的 Device ,并填充數(shù)據(jù) std::unique_ptr<Device> device = std::make_unique<Device>(fd, deviceId, devicePath, identifier); // 2.1 加載并解析輸入設(shè)備的配置文件,解析的結(jié)果保存到 EventHub::configuration 中 device->loadConfigurationLocked(); // ... // 2.2 查找輸入設(shè)備可以報告哪些種類的事件 // EV_KEY 表示按鍵事件,鍵盤類型設(shè)備可以報告此類事件 device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask); // EV_ABS 表示絕對坐標(biāo)事件,觸摸類型設(shè)備可以報告此類事件 device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask); // ... device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask); // 2.3 判斷輸入設(shè)備的類型,保存到 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 管理打開的輸入設(shè)備描述符 if (registerDeviceForEpollLocked(*device) != OK) { return; } // kernel 進(jìn)行配置 device->configureFd(); // 4. 保存正在打開的輸入設(shè)備 // 就是簡單的保存到 std::vector<std::unique_ptr<Device>> mOpeningDevices addDeviceLocked(std::move(device)); }
設(shè)備的掃描過程如下
從驅(qū)動中輸入設(shè)備廠商信息,并保存到 InputDeviceIdentifier 中。
創(chuàng)建代表輸入設(shè)備 Device 對象,并更新輸入設(shè)備的信息
- 加載并解析輸入設(shè)備的配置文件(不包括鍵盤配置文件),請參考【加載并解析輸入設(shè)備的配置】。
- 更新輸入設(shè)備能報告的事件類型,例如手機(jī)上觸摸屏能報告 EV_ABS 類型的事件,它是一個絕對坐標(biāo)事件。
- 根據(jù)設(shè)備能報告的事件類型,判斷輸入設(shè)備的類型。例如,設(shè)備能報告 EV_ABS 類型事件,那么它肯定是一個觸摸設(shè)備,類型肯定有 InputDeviceClass::TOUCH。
- epoll 監(jiān)聽輸入設(shè)備描述符的事件,其實就是實時監(jiān)聽輸入設(shè)備的輸入事件的到來。
- 保存正在打開輸入設(shè)備。其實就是把創(chuàng)建的 Device 對象保存到 mOpeningDevices 中。
EventHub 所創(chuàng)建的輸入設(shè)備的信息,可以通過 adb shell dumpsys input 查看
可以通過 dumpsys input 查看 EventHub 獲取的設(shè)備信息
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>
有幾點要說明下
- 創(chuàng)建 Device 后,加載的配置文件,對應(yīng)于這里的 ConfigurationFile。屬性 KeyLayoutFile 和 KeyCharacterMapFile 是代表鍵盤類輸入設(shè)備的按鍵布局文件和按鍵字符映射文件,它們是在按鍵事件的處理過程中加載的。
- 輸入設(shè)備 XXXTouchScreen 的類型有三個 KEYBOARD,TOUCH,TOUCH_MT,從這可以看出,一個輸入設(shè)備可以報告多種類型的事件。
加載并解析輸入設(shè)備的配置
void EventHub::Device::loadConfigurationLocked() { // EventHub::configurationFile 保存配置文件的路徑 // 注意,第二個參數(shù)為 InputDeviceConfigurationFileType::CONFIGURATION,表示加載以 idc 結(jié)尾的文件 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 保存配置文件的數(shù)據(jù) configuration = std::move(*propertyMap); } } }
加載并解析輸入設(shè)備的配置文件的過程如下
- 獲取配置文件并保存到 EventHub::configurationFile 。
- 解析并保存數(shù)據(jù)保存到 EventHub::configuration 。
下面分析的幾個函數(shù),是加載所有配置文件的基本函數(shù)
// 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. 依次從各個分區(qū)的的子目錄 /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)]; }
所有的配置文件的路徑有很多,我用兩個正則表達(dá)式表示
/[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,用于定義輸入設(shè)備的基本配置。
kl 全稱是 key layout,用于定義按鍵布局。
kcm 全稱是 key character map,用于定義按鍵字符映射。
對于文件名 name,有如下幾種情況
Vendor_XXX_Product_XXX_Version_XXX
(Version不為0的情況下)Vendor_XXX_Product_XXX
(Version為0的情況下)- 修正后的設(shè)備名(設(shè)備名中除了字母、數(shù)字、下劃線、破折號,其實字符都替換為下劃線),。
- 對于鍵盤類型輸入設(shè)備,按鍵配置文件的名字還可以為 Generic。
可以通過 dumpsys input 查看輸入設(shè)備的所有配置文件
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 (now >= mNextTimeout) { mNextTimeout = LLONG_MAX; timeoutExpiredLocked(now); } } // 3. 處理輸入設(shè)備改變 // 3.1 輸入設(shè)備改變,重新獲取輸入設(shè)備信息 if (oldGeneration != mGeneration) { inputDevicesChanged = true; inputDevices = getInputDevicesLocked(); } } // release lock // 3.2 通知監(jiān)聽者,輸入設(shè)備改變了 if (inputDevicesChanged) { mPolicy->notifyInputDevicesChanged(inputDevices); } // 4. 刷新隊列中緩存的事件,分發(fā)事件給 InputClassifier。 mQueuedListener->flush(); }
InputReader 處理合成事件的過程如下
- 使用 processEventsLocked() 處理合成事件。
- 如果設(shè)備發(fā)生改變,更新輸入設(shè)備信息后,通知監(jiān)聽者。其實就是把輸入設(shè)備信息更新到上層的 InputManagerService。
- 刷新隊列,分發(fā)事件給 InputClassifier。
本文只分析 processEventsLocked() 如何處理合成事件。
EventHub 的設(shè)備掃描,生成了兩種類型的事件,一種為 EventHubInterface::DEVICE_ADDED, 另一種為 EventHubInterface::FINISHED_DEVICE_SCAN。現(xiàn)在看下這兩種事件的處理過程
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) { // 匯總所有鍵盤類型輸入設(shè)備的meta鍵狀態(tài) // 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. 創(chuàng)建輸入設(shè)備 InputDevice std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier); // 1.2 使用InputReader的配置mConfig,對輸入設(shè)備進(jìn)行配置 device->configure(when, &mConfig, 0); device->reset(when); // 2. 保存輸入設(shè)備 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,表示輸入設(shè)備改變了 bumpGenerationLocked(); // ... }
InputReader 對 EventHubInterface::DEVICE_ADDED 類型的事件的處理過程如下
- 創(chuàng)建代表輸入設(shè)備的 InputDevice,然后使用 InputReader 的配置對它進(jìn)行配置。
- 用數(shù)據(jù)結(jié)構(gòu)保存 InputDevice,最主要是使用 mDevices 保存。
分析到這里,其實已經(jīng)明白了整個設(shè)備掃描的過程。最后,我們重點分析下 EventHub 如何創(chuàng)建輸入設(shè)備 InputDevice,以及如何配置它。詳見【創(chuàng)建與配置 InputDevice】
創(chuàng)建與配置 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(); // 創(chuàng)建 InputDevice device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(), identifier); } // InputDevice 創(chuàng)建 InputMapper 集合 device->addEventHubDevice(eventHubId); return device; } void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { if (mDevices.find(eventHubId) != mDevices.end()) { return; } // 1. 根據(jù)輸入設(shè)備類型,創(chuàng)建 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)); } // 省略其實類型的輸入設(shè)備的 InputMapper 的創(chuàng)建與保存的過程 ... // 2. 保存 InputMapper 集合 // insert the context into the devices set mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))}); // InputReader 的 mGerneration 加 1,表示設(shè)備改變 // Must change generation to flag this device as changed bumpGeneration(); }
InputReade 創(chuàng)建 InputDevice 的過程如下
- 創(chuàng)建 InputDevice 實例。
- 為 InputDevice 創(chuàng)建 InputMapper 集合。
由于一個輸入設(shè)備可能有多種類型,而每一種類型對應(yīng)一個 InputMapper,因此 InputDevice 會擁有多個 InputMapper。當(dāng)一個輸入設(shè)備上報某一種類型的事件時,InputDevice 會把這個事件交給所有的 InputMapper 處理,而 InputMapper 根據(jù)事件的類型就知道是不是要處理這個事件。
InputMapper 的作用是對輸入事件進(jìn)行加工,例如,把觸摸屏的坐標(biāo)轉(zhuǎn)換為顯示屏的坐標(biāo),然后把加工后的事件放入 QueuedInputListener 的隊列中。InputReader 把從 EventHub 獲取的所有事件都處理完畢后,會刷新 QueuedInputListener 的隊列,也就是把事件發(fā)送給 InputClassifier。
EventHub 中使用 Device 代表輸入設(shè)備,而 InputReader 使用 InputDevice 代表輸入設(shè)備,它們之間的差別就是這個 InputMapper。也就是說 InputDevice 能處理事件,而 Device 只能保存輸入設(shè)備信息。
現(xiàn)在看下如何對 InputDevice 進(jìn)行配置
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); // 保存輸入設(shè)備所有的配置文件 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獲取設(shè)備別名的配置 std::string alias = mContext->getPolicy()->getDeviceAlias(mIdentifier); if (mAlias != alias) { mAlias = alias; bumpGeneration(); } } } if (!changes || (changes & InputReaderConfiguration::CHANGE_ENABLED_STATE)) { // 從InputReader的配置mConfig中,檢測輸入設(shè)備是否被拆除在外,并相應(yīng)設(shè)置設(shè)備的 enabled 狀態(tài) 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進(jìn)行配置 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 進(jìn)行配置的數(shù)據(jù)源有如下幾個
- EventHub。
- 上層的 InputManagerService。
- InputReader 的配置 mConfig。
我們不用糾結(jié)配置 InputDevice 的數(shù)據(jù)是什么,因為可以通過 adb shell dumpsys input 導(dǎo)出 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 進(jìn)行配置。
對于觸摸屏設(shè)備而言,它的所有的 InputMapper 都有一個公共的父類 TouchInputMapper,如果這個觸摸屏支持多點觸摸,這個 InputMapper 就是 MultiTouchInputMapper,而如果只支持單點觸摸,那么這個 InputMapper 就是 SingleTouchInputMapper。
我們以手機(jī)的觸摸屏為例,它的 InputMapper 的配置過程是在 TouchInputMapper 中完成的
// 注意,參數(shù) config 是 InputReader 的配置 mConfig void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) { // 父類實現(xiàn)為空 InputMapper::configure(when, config, changes); // TouchInputMapper 還保存了 InputReader 的配置 mConfig = *config; if (!changes) { // first time only // Configure basic parameters. // 1. 配置基本的參數(shù),參數(shù)保存到 TouchInputMapper::mParameters configureParameters(); // Configure common accumulators. mCursorScrollAccumulator.configure(getDeviceContext()); mTouchButtonAccumulator.configure(getDeviceContext()); // Configure absolute axis information. // 2. 配置坐標(biāo)系 // 由子類實現(xiàn) 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() 配置基本的參數(shù)。什么是基本參數(shù)?按我的理解,就是保持不變的參數(shù)。這些基本參數(shù)會保存到 InputDevice::mParameters 中。詳見【配置基本參數(shù)】
- 通過 configureRawPointerAxes() 配置坐標(biāo)系,不過這個函數(shù)由子類實現(xiàn),對于支持多點觸摸的輸入設(shè)備,子類就是 MultiTouchInputMapper。其實就是獲取觸摸屏的坐標(biāo)系信息,例如 x, y 方向的坐標(biāo)點的信息(例如,最大值,最小值)。詳見【配置坐標(biāo)系】
- 通過 configureSurface() 配置 surface 的參數(shù)。這些參數(shù)決定了如何從觸摸屏坐標(biāo)轉(zhuǎn)換為顯示屏坐標(biāo)。【配置 Surface】
配置基本參數(shù)
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 從驅(qū)動獲取信息,決定設(shè)備類型 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); // 根據(jù)配置文件,決定設(shè)備類型 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 默認(rèn)為 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) { // 對于觸摸屏設(shè)備,mParameters.hasAssociatedDisplay 為 true, 表示必須有關(guān)聯(lián)的顯示屏? mParameters.hasAssociatedDisplay = true; if (mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN) { mParameters.associatedDisplayIsExternal = getDeviceContext().isExternal(); // 配置文件指定輸入設(shè)備關(guān)聯(lián)的顯示屏? 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. // 這里有一個非常有意思的事情,根據(jù)注釋所說,外部設(shè)備在觸摸時會被自動喚醒 // 而內(nèi)部設(shè)備,需要在配置文件中添加 touch.wake=true,才會在觸摸時被喚醒,但是為了防止在口袋中無觸摸喚醒設(shè)備,一般不配置這個屬性 mParameters.wake = getDeviceContext().isExternal(); getDeviceContext().getConfiguration().tryGetProperty(String8("touch.wake"), mParameters.wake); }
基本參數(shù) InputDevice::mParameters 基本是由配置文件決定的,還有一部分是從驅(qū)動獲取的,這些信息應(yīng)該都是“死”的。
同樣,InputDevice::mParameters 數(shù)據(jù)也是可以通過 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
配置坐標(biāo)系
void MultiTouchInputMapper::configureRawPointerAxes() { TouchInputMapper::configureRawPointerAxes(); // 1. 獲取坐標(biāo)系的信息 // 最終調(diào)用 EventHub::getAbsoluteAxisInfo(), 詢問驅(qū)動,獲取想要的數(shù)據(jù) 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. 對觸摸事件的累加器進(jìn)行配置 // 對累加器 MultiTouchMotionAccumulator 進(jìn)行配置,其實就是分配 Slot 數(shù)組 // 最后一個參數(shù)表示是否使用slot協(xié)議 mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount, true /*usingSlotsProtocol*/); } else { mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS, false /*usingSlotsProtocol*/); } }
InputMapper 配置坐標(biāo)系的過程如下
- 通過 getAbsoluteAxisInfo() 從驅(qū)動獲取坐標(biāo)系的信息,然后保存到 MultiTouchInputMapper::mRawPointerAxes。
- 對觸摸事件的累加器進(jìn)行配置
介紹下坐標(biāo)系的信息結(jié)構(gòu),mRawPointerAxes 的類型為 RawPointerAxes,代表一個坐標(biāo)系,如下
// 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(); };
每一個成員變量都代表坐標(biāo)系的某一種信息,但是非常有意思的事情是,所有成員變量的類型都為 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; } };
雖然坐標(biāo)系的每一種信息都由 RawAbsoluteAxisInfo 表示,但是并不是 RawAbsoluteAxisInfo 所有的成員變量都有效,畢竟信息都有差異的。這里介紹幾個與觸摸屏相關(guān)的成員變量所代表的意思
- RawAbsoluteAxisInfo:valid 表示坐標(biāo)系是否支持此信息。
- RawAbsoluteAxisInfo::minValue 和 RawAbsoluteAxisInfo::maxValue,它們表示 x 軸和 y 軸的坐標(biāo)范圍。
同樣地,可以通過 adb shell dumpsys input 把坐標(biāo)系的信息導(dǎo)出來
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
現(xiàn)在接著看下如何對觸摸事件累加器進(jìn)行配置
// 第三個參數(shù) usingSlotsProtocol 表示是否使用 slot 協(xié)議 void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol) { mSlotCount = slotCount; mUsingSlotsProtocol = usingSlotsProtocol; mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); delete[] mSlots; // 原來就是分配 Slot 數(shù)組 mSlots = new Slot[slotCount]; }
觸摸事件累加器的配置過程非常簡單,就是創(chuàng)建 Slot 數(shù)組。當(dāng)手指在觸摸屏上滑動時,Slot 數(shù)組中的每一個元素對應(yīng)一個手指,保存相應(yīng)手指所產(chǎn)生的一連串的坐標(biāo)事件。
配置 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. // 下面是根據(jù)屏幕方向,轉(zhuǎn)換坐標(biāo) 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. // 這里與開發(fā)者模式下的 Touch taps 功能有關(guān),也就是顯示觸摸點 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(); } }
這些信息太復(fù)雜了,不過還是可以通過 adb shell dupmsys input 導(dǎo)出來
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
小結(jié)
設(shè)備的掃描與合成事件的處理過程如下
- EventHub 創(chuàng)建并保存輸入設(shè)備 Deivce 到 mDevices 中,其中包括為輸入設(shè)備加載配置文件。
- InputReader 創(chuàng)建并保存輸入設(shè)備 InputDevice 到 mDevices 中,其中包括對 InputDevice 進(jìn)行配置,對 InputDevice 所保存的 InputMapper 進(jìn)行配置。
設(shè)備的掃描與合成事件的處理過程并不復(fù)雜,但是對輸入設(shè)備是極其繁瑣的,這些配置數(shù)據(jù)到底有何用,我們在后面分析輸入事件的處理過程中,再見分曉。
以上就是Input系統(tǒng)之InputReader處理合成事件詳解的詳細(xì)內(nèi)容,更多關(guān)于InputReader處理合成事件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android數(shù)據(jù)持久化之讀寫SD卡中內(nèi)容的方法詳解
這篇文章主要介紹了Android數(shù)據(jù)持久化之讀寫SD卡中內(nèi)容的方法,結(jié)合具體實例形式分析了Android持久化操作中針對SD卡進(jìn)行讀寫操作的相關(guān)實現(xiàn)技巧與注意事項,需要的朋友可以參考下2017-05-05Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載流程解析
這篇文章主要介紹了Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載流程,從構(gòu)造函數(shù)開始說起,我們以最簡單的調(diào)用方式舉例,當(dāng)我們使用Image.network(imageUrl)這種方式來顯示圖片時,Image組件內(nèi)部image屬性就會被賦值NetworkImage,具體操作步驟跟隨小編一起看看吧2022-05-05Android中FlowLayout組件實現(xiàn)瀑布流效果
大家好,本篇文章主要講的是Android中FlowLayout組件實現(xiàn)瀑布流效果,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01Android實現(xiàn)的數(shù)字格式化用法示例
這篇文章主要介紹了Android實現(xiàn)的數(shù)字格式化用法,結(jié)合實例形式分析了Android數(shù)學(xué)運(yùn)算中數(shù)字格式化輸出的相關(guān)技巧,需要的朋友可以參考下2016-08-08Android FTP 多線程斷點續(xù)傳下載\上傳的實例
本篇文章主要介紹了Android FTP 多線程斷點續(xù)傳下載\上傳的實例,具有一定的參考價值,有興趣的可以了解一下2017-08-08