MF99 coding 💻

keep learning; keep coding;

Android 的 KeyCode : 從 RawEvent 到 KeyEvent

在上一篇提到 KeyEvent流程 的時候,有個東西省略跳過了,這東西叫做 KeyCode

KeyCode 這東西其實在底層跟在 Android上層是不一樣的值,是透過一個 Mapping 的機制去轉換,才會得到 Android Developer 上 KeyEvent.java 中定義的 KeyCode 值。 這邊也藉機追了一下 KeyCode 的轉換過程。

以下為了避免混淆,我在敘述 KeyCode 的時候會分成 scanCodeAndroidKeyCode 作為區別, scanCode 就是直接從 driver 吐上來的不管是 scanCode 或是 usageCode。也就是 RawEvent 裡面包上來的 code。 而 AndroidKeyCode 則是轉換過後,有定義在 Android Developer 上 KeyEvent.java 中定義的 KeyCode,也就是一般在寫 Application 的時候拿的到的 Keycode。

上一篇 提到當 KeyboardInputMapper 接到了 InputDevice 的 process() function 時,除了收到 RawEvent 之外,其實在送到 processKey() function 之前就會先取得轉換過後的 AndroidKeyCode 之後,才會一起送下去。

void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    ...
        if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
            keyCode = AKEYCODE_UNKNOWN;
            flags = 0;
        }
        processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
    ...

這邊可以看到他是先去跟 EventHub 透過 mapKey(),透過 pointer 的方式去取得轉換過後的 AndroidKeyCode (不然原本 RawEvent 只有 scanCode 跟 usageCode)

status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
        int32_t* outKeycode, uint32_t* outFlags) const {
    ...
    if (device->keyMap.haveKeyLayout()) {
        if (!device->keyMap.keyLayoutMap->mapKey(scanCode, usageCode, outKeycode, outFlags)) {
            return NO_ERROR;
        }
    }
    ...

收到之後,EventHub 會去尋找 KeyLayoutMap 來尋找真正的 KeyEvent。 這時候就會跳到 frameworks/base/libs/androidfw/KeyLayoutMap.cpp

status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode,int32_t* outKeyCode, uint32_t* outFlags)const{
    const Key* key = getKey(scanCode, usageCode);
    ...
}
const KeyLayoutMap::Key* KeyLayoutMap::getKey(int32_t scanCode, int32_t usageCode) const {
    ...
    ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
    if (index >= 0) {
        return &mKeysByScanCode.valueAt(index);
    }
    ...
}

到了 KeyLayoutMap 中,他就是去 mKeysByScanCode 裡面用 scanCode 然後立馬就找到 AndroidKeyCode 了!! 看起來整個 KeyCode 對照表就是在這裡!

但是這個 mKeysByScanCode 鬼東西的資料內容哪來的!?

這邊要先提到兩個東西,一個是 Generic.kl 一個是 KeycodeLabels.h 前面的是位於 frameworks/base/data/keyboards/Generic.kl 後面的是位於 frameworks/base/include/androidfw/KeycodeLabels.h

簡單來說 Android 在轉換 KeyCode 的時候並不是有一個 Map 直接對照 scanCode -> AndroidKeyCode 因為為了能夠跟硬體開發商之間保留一些彈性,所以在中間加上了一個 Label 來對照 所以實際的轉換過程其實經過兩次 Mapping 的動作 1. scanCode -> Label 2. Label -> AndroidKeyCode 這邊稍微局部列一下 Generic.kl 跟 KeycodeLabels.h 的內容,看起來會更有感覺 (以 Volume Up / Down 為例子)

...
key 114   VOLUME_DOWN
key 115   VOLUME_UP
...
static const KeycodeLabel KEYCODES[] = {
    ...
    { "VOLUME_UP", 24 },
    { "VOLUME_DOWN", 25 },
    ...
}

Volume Up 來說,其中 scanCode 為 115,AndroidKeyCode 為 24, Label 就是 VOLUME_UP 總歸來說,KeycodeLabels.h 就是 Android Open Source Project 裡面定義好的 Label -> KeyCode 對照表 至於 Generic.kl ,或是其實廠商可以另外撰寫或是根據硬體特性新增自己的 xxx.kl 檔案,只要在建置的時候放進去就可以。 這樣的機制,讓製造商有多了一些彈性。 例如:假設某間公司出的變形平板,平板上方有音量控制鍵,然後在底座的鍵盤上也有快速鍵可以控制音量。 這時候這兩個硬體按鍵,原則上送出來的 scanCode 是不一樣的(因為是不同硬體),但是對於 Android 來說都是音量控制。 所以廠商就可以在自行定義的 xxx.kl 中分別指定不同的 scanCode 但是都對應到 VOLUME_UP 的 Label。

好,再來回到 mKeysByScanCode

在 KeyLayoutMap 在初始化的時候,會去進行讀取 *.kl 檔案的動作 (kl檔案會儲存於 device 中的 /system/usr/keylayout/ 底下) 然後再去做 parsing,然後對照著 KeycodeLabels.h 裡面定義的 AndroidKeyCode 塞到一個 Map 中 而這個 Map 就是 mKeysByScanCode !

這邊就不再詳細寫讀檔還有 parsing 的細節,有興趣的可以自行參考 frameworks/base/libs/androidfw/KeyLayoutMap.cpp frameworks/base/libs/androidfw/Keyboard.cpp frameworks/base/include/androidfw/KeycodeLabels.h