MF99 coding 💻

keep learning; keep coding;

Android 的 KeyEvent : 從 EventHub 到 PhoneWindowManager

最近追蹤了一下 Android 4.3 的 source,並且追蹤了 KeyEvent 一路從 EventHub.cpp 到 PhoneWondowManager.java 的流程 這邊順便記錄一下。

AndroidLinux kernal 的部份,所有的 Input Event 都會使用 /dev/input/event0~X 的 device node 要 Monitoring Input Event,基本上可以去 pulling 這幾個 node (但是拿到的會是 RawEvent Data,而且應該會是顯示亂碼)

或是可以利用 adb shell getevent 的指令,一來他會列出這個 node 現在使用的 device name 是什麼 (例如鍵盤、gpio-key或是 touch screen等)也會把 Raw Event 轉換成比較容易了解的格式,以及數字,在 Debug 上也蠻好用的。

切入正題,目前看到 Android 4.3 KeyEvent 的流程大致上為:

EventHub.cpp 基本上去監控 /dev/input/eventX 的 node,但是只是提供 function 來讓外部存取。 一開始是由 frameworks/base/service/input/InputManager.cpp 中會建立 InputReaderThread 並且在初始化完成之後開始 run。

接下來的 code 就會跑到 frameworks/base/service/input/InputReader.cpp 裡面 這個 Thread 基本上就是去做 pulling 的動作,並且實作在 loopOnce 這個 function

void InputReader::loopOnce() {
    ...
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    ...
        processEventsLocked(mEventBuffer, count);
    ...
}

在這個 function 裡面會去呼叫到 mEventHub->getEvents() 並取得最原始的 RawEvent 接著會丟到 processEventsLocked() 然後 processEventsForDeviceLocked()

void InputReader::processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    ...
    InputDevice* device = mDevices.valueAt(deviceIndex);
    ...
    device->process(rawEvents, count);
}

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    ...
    for (size_t i = 0; i < numMappers; i++) {
        InputMapper* mapper = mMappers[i];
        mapper->process(rawEvent);
    }
}  

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
            int32_t scanCode, uint32_t policyFlags) {
    ...
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
    getListener()->notifyKey(&args);  
}

接著會根據目前 Event 的 device 種類,丟到 InputDeviceprocess() 再來會把這個 RawEvent 丟給所有的 InputMapperprocess() 看誰要處理 以 KeyEvent 來說,會處理的會是 KeyboardInputMapper (一樣位於 InputReader.cpp 內) 所以由 KeyboardInputMapper->process() 然後送到 processKey() function

void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
    mArgsQueue.push(new NotifyKeyArgs(*args));
}

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}

接著會跳到 frameworks/base/service/input/InputListener.cpp 中,剛剛 processKey() 中會把這些 Event 的資訊丟到 QueuedInputListener::notifyKey() 然後 push 到 mArgqueue 之中,每次flush的時候交給 mInnerListener 去處理

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    ...
    KeyEvent event;
    event.initialize(args->deviceId, args->source, args->action,
            flags, args->keyCode, args->scanCode, metaState, 0,
            args->downTime, args->eventTime);
    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
    ...
}

再來剛剛的 mListener 其實就是 InputDispatcher 所以接下來會接到 frameworks/base/service/input/InputDispatcher.cpp 之中的 notifyKey function,並在此第一次打包成 KeyEvent 的格式 (還不是 Java 物件) 然後送到 mPolicy->interceptKeyBeforeQueueing()

void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags){
    ...
  JNIEnv* env = jniEnv();
    jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
    if (keyEventObj) {
        wmActions = env->CallIntMethod(mServiceObj,
                    gServiceClassInfo.interceptKeyBeforeQueueing,
                    keyEventObj, policyFlags, isScreenOn);
        ...
    }
}

而這個 mPolict 就是 InputManagerService 的 native 物件,位於 frameworks/base/service/jni/com_android_server_input_ImputManagerService.cpp 然後再這裡會透過 JNI 的 function android_view_KeyEvent_fromNative() 把底層的 KeyEvent 轉成 Java 的 KeyEvent 物件 在下來透過 native callback 的方式,把做好的 KeyEvent 物件(Java版) 丟回 Java層的 InputManagerService,位置在 frameworks/base/service/java/com/android/server/input/InputManagerService.java 裡面的 interceptKeyBeforeQueueing() 接著裡面也只做一件事,把這個 Event 繼續 pass 給 mWindowManagerCallbacks 也就是 Android 上層最早接受到 KeyEvent 的 PhoneWindowManager 其位置在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

所有 Activity 會收到的 KeyEvent,或是系統會前置處理的 KeyEvent (比方說 HOME鍵,一些實體鍵盤的快捷鍵) 都是由這個位置開始的,藉由這次工作上剛好有機會去 trace 這一串 call flow,也順便記錄一下,未來有需要的時候可以參考。