Android U Input系统:InputReader 加工按键事件

2,169 阅读9分钟

从本文开始,分析下按键事件的处理和分发。

EventHub 获取按键事件

// EventHub.cpp

std::vector<RawEvent> EventHub::getEvents(int timeoutMillis) {
    std::scoped_lock _l(mLock);

    std::array<input_event, EVENT_BUFFER_SIZE> readBuffer;

    std::vector<RawEvent> events;
    bool awoken = false;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

        // ...

        // Grab the next input event.
        bool deviceChanged = false;
        // 2. 遍历处理所有输入事件
        while (mPendingEventIndex < mPendingEventCount) {
            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];

            // ...

            // This must be an input event
            if (eventItem.events & EPOLLIN) {
                int32_t readSize =
                        read(device->fd, readBuffer.data(),
                             sizeof(decltype(readBuffer)::value_type) * readBuffer.size());
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                    // ...
                } else if (readSize < 0) {
                    // ...
                } else if ((readSize % sizeof(struct input_event)) != 0) {
                    // ...
                } else {
                    const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;

                    const size_t count = size_t(readSize) / sizeof(struct input_event);
                    for (size_t i = 0; i < count; i++) {
                        struct input_event& iev = readBuffer[i];
                        // 2.1 把事件加入都 events 中
                        events.push_back({
                                .when = processEventTimestamp(iev),
                                .readTime = systemTime(SYSTEM_TIME_MONOTONIC),
                                .deviceId = deviceId,
                                .type = iev.type,
                                .code = iev.code,
                                .value = iev.value,
                        });
                    }
                    if (events.size() >= EVENT_BUFFER_SIZE) {
                        // The result buffer is full.  Reset the pending event index
                        // so we will try to read the device again on the next iteration.
                        mPendingEventIndex -= 1;
                        break;
                    }
                }
            } else if (eventItem.events & EPOLLHUP) {
                // ...
            } else {
                // ...
            }
        }


        if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
            // ...
        }

        if (deviceChanged) {
            continue;
        }

        // 3. events 现在有事件,跳出无限循环
        if (!events.empty() || awoken) {
            break;
        }


        mPendingEventIndex = 0;

        mLock.unlock(); // release lock before poll

        // 1. 监听到输入设备产生事件
        // 事件保存到 mPendingEventItems
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);

        mLock.lock(); // reacquire lock after poll

        if (pollResult == 0) {
            // ...
            break;
        }

        if (pollResult < 0) {
            // ...
        } else {
            // Some events occurred.
            // 保存即将要处理的事件的数量
            mPendingEventCount = size_t(pollResult);
        }
    }

    // 4. 返回读到的事件
    return events;
}


对于输入设备产生的事件,无论是 key event 还是 motion event,EventHub 统一使用结构体 RawEvent 来保存数据,如下

// EventHub.h

struct RawEvent {
    nsecs_t when;
    nsecs_t readTime;
    int32_t deviceId;
    int32_t type;
    int32_t code;
    int32_t value;
};

我们可以通过 adb shell getevent 命令,观察按下和抬起电源键时,驱动所产生的元数据,如下

/dev/input/event1: 0001 0074 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0001 0074 00000000
/dev/input/event1: 0000 0000 00000000

每一行的最后三个数据,分别对一个 RawEvent 结构体中的 type, code, value。但是,这些数据并不直观,我们可以通过 adb shell getevent -l 观察,如下

/dev/input/event1: EV_KEY       KEY_POWER            DOWN
/dev/input/event1: EV_SYN       SYN_REPORT           00000000
/dev/input/event1: EV_KEY       KEY_POWER            UP
/dev/input/event1: EV_SYN       SYN_REPORT           00000000

第一行是按下电源键产生的事件数据,第三行是抬起电源键所产生的事件数据,而第二行和第四行是一个同步事件,它不代表电源键的任何事件,它只是告诉输入系统,需要同步之前的数据了。

InputReader 处理按键事件

// InputReader.cpp

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    // Copy some state so that we can access it outside the lock later.
    bool inputDevicesChanged = false;
    std::vector<InputDeviceInfo> inputDevices;
    
    // InputReader 处理事件时,会生成新的事件,保存到 notifyArgs
    std::list<NotifyArgs> notifyArgs;
    
    // ...

    // 1. 读取事件
    std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);

    { // acquire lock
        std::scoped_lock _l(mLock);
        mReaderIsAliveCondition.notify_all();

        // 2. 处理事件
        if (!events.empty()) {
            notifyArgs += processEventsLocked(events.data(), events.size());
        }

        // ...
    } // release lock

    // ...

    // 3.按键事件加入到 mQueuedListener 队列
    notifyAll(std::move(notifyArgs));

    // 3.刷新 mQueuedListener 队列,把事件发送到下一环
    mQueuedListener.flush();
}

//事件加入到 mQueuedListener 队列
void InputReader::notifyAll(std::list<NotifyArgs>&& argsList) {
    for (const NotifyArgs& args : argsList) {
        mQueuedListener.notify(args);
    }
}

InputReader 从 EventHub 获得了按键事件后,会对它进行加工处理,然后把加工后的数据重新包装,最后分发给下一环。

处理按键事件

// InputReader.cpp

std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    std::list<NotifyArgs> out;
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        
        // 遍历所有的 RawEvent
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            int32_t deviceId = rawEvent->deviceId;

            // 获取可以批量处理事件数量
            while (batchSize < count) {
                // 遇到合成事件,或者不属于同一设备的事件,就停止
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||
                    rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }

            if (debugRawEvents()) {
                ALOGD("BatchSize: %zu Count: %zu", batchSize, count);
            }

            // InputReader 对 raw event 进行批量的加工处理,并返回加工后的事件
            out += processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            // ...
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
    
    // 返回加工后的事件
    return out;
}

std::list<NotifyArgs> InputReader::processEventsForDeviceLocked(int32_t eventHubId,
                                                                const RawEvent* rawEvents,
                                                                size_t count) {
    // 获取输入设备 InputDevice
    auto deviceIt = mDevices.find(eventHubId);
    if (deviceIt == mDevices.end()) {
        ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);
        return {};
    }
    std::shared_ptr<InputDevice>& device = deviceIt->second;

    // InputDevice 没有 InputMapper,就不能处理事件
    if (device->isIgnored()) {
        // ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return {};
    }

    // InputDevice 批量处理事件
    return device->process(rawEvents, count);
}

InputReader 批量地把按键事件,交给对应的输入设备 InputDevice 处理,如下

// InputDevice.cpp

std::list<NotifyArgs> InputDevice::process(const RawEvent* rawEvents, size_t count) {
    // Process all of the events in order for each mapper.
    // We cannot simply ask each mapper to process them in bulk because mappers may
    // have side-effects that must be interleaved.  For example, joystick movement events and
    // gamepad button presses are handled by different mappers but they should be dispatched
    // in the order received.
    std::list<NotifyArgs> out;

    // 遍历所有事件,逐个处理
    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {

        // debug 版本,打印如下 raw events 数据
        if (debugRawEvents()) {
            const auto [type, code, value] =
                    InputEventLookup::getLinuxEvdevLabel(rawEvent->type, rawEvent->code,
                                                         rawEvent->value);
            ALOGD("Input event: eventHubDevice=%d type=%s code=%s value=%s when=%" PRId64,
                  rawEvent->deviceId, type.c_str(), code.c_str(), value.c_str(), rawEvent->when);
        }

        if (mDropUntilNextSync) {
            // ...
        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
            // ...
        } else {
            // 交给所有的 InputMapper 处理事件
            for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {
                out += mapper.process(rawEvent);
            });
        }
        --count;
    }
    return out;
}

虽然说 InputDevice 是批量处理按键事件,但是它是逐个地把按键事件,交给 InputDevice 的所有 InputMapper 处理。而 InputMapper 只会处理自己感兴趣的事件,例如, KeyboardInputMapper 只处理按键事件,而不会处理触摸事件。

对于按键事件,是由 KeyboardInputMapper 来处理的,如下

// KeyboardInputMapper.cpp

std::list<NotifyArgs> KeyboardInputMapper::process(const RawEvent* rawEvent) {
    std::list<NotifyArgs> out;

    // 处理 EV_MSC(type) + MSC_SCAN(code) 事件
    mHidUsageAccumulator.process(*rawEvent);

    switch (rawEvent->type) {
        case EV_KEY: {
            // RawEvent::code 也可以称之为 scan code
            // 这个命令与硬件产生事件的原理有关
            int32_t scanCode = rawEvent->code;
            
            if (isSupportedScanCode(scanCode)) {
                // 注意,RawEvent::value 不为 0,代表按键的按下事件
                out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0,
                                  scanCode, mHidUsageAccumulator.consumeCurrentHidUsage());
            }
            break;
        }
    }
    return out;
}

std::list<NotifyArgs> KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down,
                                                      int32_t scanCode, int32_t usageCode) {
    std::list<NotifyArgs> out;
    int32_t keyCode;
    int32_t keyMetaState;
    uint32_t policyFlags;

    // 1. 按键映射
    // 把 scan code 转化为 key code,并获取标志位,标志位保存到 policyFlags
    if (getDeviceContext().mapKey(scanCode, usageCode, mMetaState, &keyCode, &keyMetaState,
                                  &policyFlags)) {
        // ...
    }

    nsecs_t downTime = when;
    
    // 从 mKeyDowns 中根据 scan code 获取 key down 数据的 index
    std::optional<size_t> keyDownIndex = findKeyDownIndex(scanCode);
    
    if (down) { // 处理 key down
    
        // 根据 orientation 旋转 key code,一般的按键设备不支持
        // Rotate key codes according to orientation if needed.
        if (mParameters.orientationAware) {
            keyCode = rotateKeyCode(keyCode, getOrientation());
        }

        if (keyDownIndex) {
            // ...
        } else {
            // ...

            // 使用 KeyDown 保存按键按下的数据,主要包括 scan code 和 key code
            KeyDown keyDown;
            keyDown.keyCode = keyCode;
            keyDown.scanCode = scanCode;
            keyDown.downTime = when;
            // KeyDown 保存到集合 mKeyDowns
            mKeyDowns.push_back(keyDown);
        }
    } else { // 处理 key up
        // Remove key down.
        if (keyDownIndex) {
            // key up, be sure to use same keycode as before in case of rotation
            keyCode = mKeyDowns[*keyDownIndex].keyCode;
            downTime = mKeyDowns[*keyDownIndex].downTime;

            // 按键抬起时,从 mKeyDowns 移除 key down 数据
            mKeyDowns.erase(mKeyDowns.begin() + *keyDownIndex);
        } else {
            // ...
        }
    }

    // ...

    // 2. 返回加工后的按键事件数据
    out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
                                   mSource, getDisplayId(), policyFlags,
                                   down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
                                   AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState,
                                   downTime));
    return out;
}

对于手机/平板来说,处理按键事件( 系统称之为加工事件 ),主要是把 scan code 转化为 key code,然后使用结构体 NotifyKeyArgs 保存加工后的按键事件数据,并作为加工按键事件的结果返回。

为何要把 scan code 转化为 key code ? 不同的输入设备上,同一种功能的按键,例如,电源键,它们的 scan code 是不同的。Android 系统为了统一处理同一种功能的按键,需要把不同的 scan code 映射为统一的 key code,如下

// EventHub.cpp

status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
                          int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
    std::scoped_lock _l(mLock);

    // 获取输入设备
    Device* device = getDeviceLocked(deviceId);

    status_t status = NAME_NOT_FOUND;

    if (device != nullptr) {
        // 1. 首先由 key character map 映射
        const std::shared_ptr<KeyCharacterMap> kcm = device->getKeyCharacterMap();
        if (kcm) {
            if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
                // 注意,kcm 转换成功,是没有 policy flags
                *outFlags = 0;
                status = NO_ERROR;
            }
        }

        // 2. 如果上一步没转换成功,接着由 key layout 映射
        if (status != NO_ERROR && device->keyMap.haveKeyLayout()) {
            // 注意,kl 转化,是有 policy flags
            if (!device->keyMap.keyLayoutMap->mapKey(scanCode, usageCode, outKeycode, outFlags)) {
                status = NO_ERROR;
            }
        }

        if (status == NO_ERROR) {
            if (kcm) { // kcm 再处理
                // 上层可以通过 InputManager#remapModifierKey(fromKey, toKey) 
                // 把 kcm 映射的 key code, 重新映射为一个新的 key code
                // 但是这是一个测试 API,通常用不到
                *outKeycode = kcm->applyKeyRemapping(*outKeycode);

                // Remap keys based on Key behavior defined in KCM file
                // kcm 根据 meta 键(Ctrl、Alt、Shift) 重新转换 key code 和 meta state
                std::tie(*outKeycode, *outMetaState) =
                        kcm->applyKeyBehavior(*outKeycode, metaState);
            } else { // kl 再处理
                *outMetaState = metaState;
            }
        }
    }

    if (status != NO_ERROR) {
        // ...
    }

    return status;
}

有两个文件,可以用于按键映射

  1. key character map 文件,简称 kcm 文件。它主要是对键盘的按键进行映射,它会考虑 meta 键(shift、Ctrl、Alt,等)状态。例如,如果 shift 键按下,再按下 f 键,那么 kcm 把会 f 键的 scan code 转化为大写的 F 的 key code。
  2. key layout 文件,简称 kl 文件。与 kcm 一样,同样可以把 scan code 转化为 key code,但是它要简单的多,不会考虑 meta 键状态。因此 kl 文件,主要是手机/平板来使用。另外,它还可以附带一个标志位,Android 系统可以针对不同标志位,响应不同的行为。

根据前面文章的分析可知,kcm 和 kl 文件,是在扫描设备时加载的,可以通过 adb shell dump input 查看这两个文件路径,如下

    9: pmic_pwrkey
      Classes: KEYBOARD
      Path: /dev/input/event1
      
      KeyLayoutFile: /system/usr/keylayout/Generic.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm

在两个文件中同时搜索 POWER 关键字,在我的手机上,我只在 kl 文件中,查找到电源键的按键映射配置,如下

key 116   POWER
key 152   POWER
key 699   POWER

那么,到底哪个数据有效呢?可以通过 adb shell getevent 查看电源键上报的的 scan code

/dev/input/event1: 0001 0074 00000001

电源键的 scan code 为 0074,它是十六进制,转化为十进制就是 116,因此使用的就是 key 116 POWER 对按键进行映射的。

电源键的 scan code ,映射的值是一个字符串 POWER,然后在根据 frameworks/native/include/android/keycodes.h 转换为上层所使用的 int 值,如下

// frameworks/native/include/android/keycodes.h

enum {
    /** Power key. */
    AKEYCODE_POWER           = 26,
}

上层的 KeyEvent.java 中定义的电源键值,恰好也是 26,如下

// KeyEvent.java

/** Key code constant: Power key. */
public static final int KEYCODE_POWER           = 26;

关于 kcm、kl 文件,大家可以参考 Google 给的官方文档

  1. key layout files : source.android.google.cn/docs/core/i…
  2. key charater map files : source.android.google.cn/docs/core/i…

刷新 QueuedInputListener 队列

KeyboardInputMapper 处理完按键事件后,创建 NotifyKeyArgs 作为新的按键事件。之后,NotifyKeyArgs 保存到 InputReader::mQueuedListener 的队列中,最后刷新 mQueuedListener 的队列,如下

// InputListener.cpp

void QueuedInputListener::flush() {
    for (const NotifyArgs& args : mArgsQueue) {
        // 通知下一环处理
        mInnerListener.notify(args);
    }
    mArgsQueue.clear();
}

void InputListenerInterface::notify(const NotifyArgs& generalArgs) {
    Visitor v{
            // ...
            
            // 处理按键事件
            [&](const NotifyKeyArgs& args) { notifyKey(args); },
            
            // ...
    };
    std::visit(v, generalArgs);
}

根据第一篇文章的分析,InputReader 的下一环是 UnwantedInteractionBlocker,因此调用它的 notifyKey() 来处理按键事件

// UnwantedInteractionBlocker.cpp

void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs& args) {
    // 加入队列
    mQueuedListener.notifyKey(args);
    // 刷新队列
    mQueuedListener.flush();
}

UnwantedInteractionBlocker 没有做任何处理,直接交给下一环 InputProcessor

void InputProcessor::notifyKey(const NotifyKeyArgs& args) {
    // pass through
    mQueuedListener.notifyKey(args);
    mQueuedListener.flush();
}

InputProcessor 同样没做什么处理,直接交给下一环 InputDispatcher。 而 InputDispatcher 会分发事件给窗口,这个过程比较长,下一篇文章继续分析。