Android 按鍵映射分析

android 能夠將不同的低層 scancode 轉化成上層使用的統一的 keycode (以下分析爲 android 2.2 froyo 的)。下面說的幾個相關的源代碼文件都在 framework/base/libs/ui 下。

EventHub? .cpp

先看看下面這段代碼:
// 在 open_device 函數裏
if ((device->classes&CLASS_KEYBOARD) != 0) { 
        char devname[PROPERTY_VALUE_MAX];
        char keylayoutFilename[300];
        
        const char* root = getenv("ANDROID_ROOT");
        property_get("persist.sys.keylayout", devname, "qwerty");
        snprintf(keylayoutFilename, sizeof(keylayoutFilename),
        "%s/usr/keylayout/%s.kl", root, devname);
        bool defaultKeymap = access(keylayoutFilename, R_OK);
        if (defaultKeymap) {
                strcpy(devname, "qwerty");
                snprintf(keylayoutFilename, sizeof(keylayoutFilename),
                "%s/usr/keylayout/%s.kl", root, devname);
        }    
        LOGI("keylayout = %s, Filename = %s", devname, keylayoutFilename);
        device->layoutMap->load(keylayoutFilename);
這段代碼是打開鍵盤設備,並讀取按鍵映射表的。從代碼裏可以看得到映射文件是從 root/usr/keylayout/qwerty.kl (root 一般是 system)下讀取的(這個 qwerty.kl 各個設備廠商應該可以自己改,我看的 x86 的默認使用的是這個)。

kl 文件

這個文件就是 android 的按鍵映射文件,結構如下:

BEGIN SCANCODE KEYCODE FLAG
key 1 BACK WACK_DROPPED
key 116 POWER WACK

這裏配合看下下面的代碼,在 KeyLayoutMap? .cpp 裏:

status_t
KeyLayoutMap::load(const char* filename)
{
        int fd = open(filename, O_RDONLY);
        if (fd < 0) {
                LOGE("error opening file=%s err=%s\n", filename, strerror(errno));
                m_status = errno;
                return errno;
        }
        
        off_t len = lseek(fd, 0, SEEK_END);
        off_t errlen = lseek(fd, 0, SEEK_SET);
        if (len < 0 || errlen < 0) {
                close(fd);
                LOGE("error seeking file=%s err=%s\n", filename, strerror(errno));
                m_status = errno;
                return errno;
        }
        
        char* buf = (char*)malloc(len+1);
        if (read(fd, buf, len) != len) {
                LOGE("error reading file=%s err=%s\n", filename, strerror(errno));
                m_status = errno != 0 ? errno : ((int)NOT_ENOUGH_DATA);
                return errno != 0 ? errno : ((int)NOT_ENOUGH_DATA);
        }
        errno = 0;
        buf[len] = '\0';
        
        int32_t scancode = -1;
        int32_t keycode = -1;
        uint32_t flags = 0;
        uint32_t tmp;
        char* end;
        status_t err = NO_ERROR;
        int line = 1;
        char const* p = buf;
        enum { BEGIN, SCANCODE, KEYCODE, FLAG } state = BEGIN;
        while (true) {
                String8 token = next_token(&p, &line);
                if (*p == '\0') {
                        break;
                }
                switch (state)
                {
                        case BEGIN:
                        if (token == "key") {
                                state = SCANCODE;
                        } else {
                                LOGE("%s:%d: expected key, got '%s'\n", filename, line,
                                token.string());
                                err = BAD_VALUE;
                                goto done;
                        }
                        break;
                        case SCANCODE:
                        scancode = strtol(token.string(), &end, 0);
                        if (*end != '\0') {
                                LOGE("%s:%d: expected scancode (a number), got '%s'\n",
                                filename, line, token.string());
                                goto done;
                        }
                        //LOGI("%s:%d: got scancode %d\n", filename, line, scancode );
                        state = KEYCODE;
                        break;
                        case KEYCODE:
                        keycode = token_to_value(token.string(), KEYCODES);
                        //LOGI("%s:%d: got keycode %d for %s\n", filename, line, keycode, token.string() );
                        if (keycode == 0) {
                                LOGE("%s:%d: expected keycode, got '%s'\n",
                                filename, line, token.string());
                                goto done;
                        }
                        state = FLAG;
                        break;
                        case FLAG:
                        if (token == "key") {
                                if (scancode != -1) {
                                        //LOGI("got key decl scancode=%d keycode=%d"
                                        //       " flags=0x%08x\n", scancode, keycode, flags);
                                        Key k = { keycode, flags };
                                        m_keys.add(scancode, k);
                                        state = SCANCODE;
                                        scancode = -1;
                                        keycode = -1;
                                        flags = 0;
                                        break;
                                }
                        }
                        tmp = token_to_value(token.string(), FLAGS);
                        //LOGI("%s:%d: got flags %x for %s\n", filename, line, tmp, token.string() );
                        if (tmp == 0) {
                                LOGE("%s:%d: expected flag, got '%s'\n",
                                filename, line, token.string());
                                goto done;
                        }
                        flags |= tmp;
                        break;
                }
        }
        if (state == FLAG && scancode != -1 ) {
                //LOGI("got key decl scancode=%d keycode=%d"
                //       " flags=0x%08x\n", scancode, keycode, flags);
                Key k = { keycode, flags };
                m_keys.add(scancode, k);
        }
        
        done:
        free(buf);
        close(fd);
        
        m_status = err;
        return err;
}

可以看得出,android 會解析 kl 文件裏的項目,然後把解析後的結果保存到一個 Vector 結構裏(android 自己寫一個 vector,不是 c++ std 的)。 kl 文件的每一項分爲4段:

  • BEGIN
這一段統一是 "key",應該是按鍵映射的標識。

  • SCANCODE
這一段直接將字符串轉化爲數字,也就是說 kl 文件裏的這一段存儲的是數字。這裏值就是從低層硬件讀取出來值。也就是我們在 OM 項目中, vfb 應該發送給 android 的值。這個值不是 android sdk 中描述的值(這個值是上層應用使用的)。不同的硬件會不一樣。

  • KEYCODE
這個值就是 android sdk 裏描述的啦。不過這一段有很多都是字符串,不是直接保存數值的,這個有個轉化關係,具體的後面再說。

  • FLAG
這裏目前來說就2個值 WAKE 和 WAKE_DROPPED 。好像 WAKE 代表可以在鎖機狀態下喚醒屏幕。

KeycodeLabels? .h

上面說了 KEYCODE 那裏很多是保存字符串的,那麼轉化的地方就在這個文件裏(這個文件在 framework/base/include/ui 下):
struct KeycodeLabel {
        const char *literal;
        int value;
};

// 這裏不貼全了,都在這
static const KeycodeLabel KEYCODES[] = { 
        { "SOFT_LEFT", 1 },
        { "SOFT_RIGHT", 2 },
        { "HOME", 3 },
        { "BACK", 4 },
        { "CALL", 5 },
        { "ENDCALL", 6 },
        { "0", 7 },
        { "1", 8 },
        { "2", 9 },
        { "3", 10 },
        { "4", 11 },
        { "5", 12 },
        { "6", 13 },
        { "7", 14 },
        { "8", 15 },
        { "9", 16 },
        { "STAR", 17 },
        { "POUND", 18 },
        { "DPAD_UP", 19 },
        ... ...

前面說的那個 load 代碼裏 KEYCODE 那一段的 keycode = token_to_value(token.string(), KEYCODES); 中的 token_to_value 就是通過上面這個數組來得到對應的數值的。這個數值就是 android sdk 裏寫的給上層應用程序使用的按鍵值了。所以一般來說,我們修改 kl 文件就可以改變按鍵映射了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章