欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Input系統(tǒng)之InputReader處理合成事件詳解

 更新時間:2022年11月11日 09:57:38   作者:大胃粥  
這篇文章主要為大家介紹了Input系統(tǒng)之InputReader處理合成事件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

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.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. 處理輸入設(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)文章

  • DownloadManager實現(xiàn)文件下載功能

    DownloadManager實現(xiàn)文件下載功能

    這篇文章主要為大家詳細(xì)介紹了DownloadManager實現(xiàn)文件下載功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Android數(shù)據(jù)持久化之讀寫SD卡中內(nèi)容的方法詳解

    Android數(shù)據(jù)持久化之讀寫SD卡中內(nèi)容的方法詳解

    這篇文章主要介紹了Android數(shù)據(jù)持久化之讀寫SD卡中內(nèi)容的方法,結(jié)合具體實例形式分析了Android持久化操作中針對SD卡進(jìn)行讀寫操作的相關(guān)實現(xiàn)技巧與注意事項,需要的朋友可以參考下
    2017-05-05
  • Kotlin語言使用WebView示例介紹

    Kotlin語言使用WebView示例介紹

    隨著后臺技術(shù)的不斷發(fā)展,App前端的應(yīng)用都布置了Web頁面的界面,這個界面就是由WebView組件渲染出來的。WebView由如下優(yōu)點:可以直接顯示和渲染W(wǎng)eb頁面或者網(wǎng)頁;可以直接調(diào)用網(wǎng)絡(luò)上或者本地的html文件,也可以和JavaScript交互使用
    2022-09-09
  • Flutter系統(tǒng)網(wǎng)絡(luò)圖片加載流程解析

    Flutter系統(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-05
  • Android中FlowLayout組件實現(xiàn)瀑布流效果

    Android中FlowLayout組件實現(xiàn)瀑布流效果

    大家好,本篇文章主要講的是Android中FlowLayout組件實現(xiàn)瀑布流效果,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • Android如何快速適配暗黑模式詳解

    Android如何快速適配暗黑模式詳解

    微信在前段時間的更新中也實現(xiàn)了暗黑模式,而蘋果系統(tǒng)也早就支持暗黑模式,Android也一樣可以實現(xiàn),下面這篇文章主要給大家介紹了關(guān)于Android如何快速適配暗黑模式的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • Android實現(xiàn)的數(shù)字格式化用法示例

    Android實現(xiàn)的數(shù)字格式化用法示例

    這篇文章主要介紹了Android實現(xiàn)的數(shù)字格式化用法,結(jié)合實例形式分析了Android數(shù)學(xué)運(yùn)算中數(shù)字格式化輸出的相關(guān)技巧,需要的朋友可以參考下
    2016-08-08
  • Android FTP 多線程斷點續(xù)傳下載\上傳的實例

    Android FTP 多線程斷點續(xù)傳下載\上傳的實例

    本篇文章主要介紹了Android FTP 多線程斷點續(xù)傳下載\上傳的實例,具有一定的參考價值,有興趣的可以了解一下
    2017-08-08
  • 淺談關(guān)于android軟鍵盤彈出問題

    淺談關(guān)于android軟鍵盤彈出問題

    本篇文章主要介紹了淺談關(guān)于android軟鍵盤彈出問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • android TabLayout使用方法詳解

    android TabLayout使用方法詳解

    這篇文章主要為大家詳細(xì)介紹了android TabLayout使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08

最新評論