移植android的键盘和按键
(1)Android使用标准的linux输入事件设备(/dev/input目录下)和驱动,按键定义在内核include/linux/input.h文件中,
按键定义形式如下:
#define KEY_ESC            1
#define KEY_1            2
#define KEY_2            3

(2)内核中(我的平台是arch/arm/mach-mmp/merlin.c文件)中按键的定义如下形式:
static struct gpio_keys_button btn_button_table[] = {
    [0] = {
        .code            =    KEY_F1,
        .gpio            =    MFP_PIN_GPIO2,
        .active_low        =    1,        /* 0 for down 0, up 1; 1 for down 1, up 0 */
        .desc            =    "H_BTN button",
        .type            =    EV_KEY,
        /* .wakeup            = */
        .debounce_interval    =    10,        /* 10 msec jitter elimination */
    },
    [1] = {
        .code            =    KEY_F2,
        .gpio            =    MFP_PIN_GPIO3,
        .active_low        =    1,        /* 0 for down 0, up 1; 1 for down 1, up 0 */
        .desc            =    "O_BTN button",
        .type            =    EV_KEY,
        /* .wakeup            = */
        .debounce_interval    =    10,        /* 10 msec jitter elimination */
    },
    [2] = {
        .code            =    KEY_F4,
        .gpio            =    MFP_PIN_GPIO1,
        .active_low        =    1,        /* 0 for down 0, up 1; 1 for down 1, up 0 */
        .desc            =    "S_BTN button",
        .type            =    EV_KEY,
        /* .wakeup            = */
        .debounce_interval    =    10,        /* 10 msec jitter elimination */
    },
};
static struct gpio_keys_platform_data gpio_keys_data = {
    .buttons  = btn_button_table,
    .nbuttons = ARRAY_SIZE(btn_button_table),
};

static struct platform_device gpio_keys = {
    .name = "gpio-keys",
    .dev  = {
        .platform_data = &gpio_keys_data,
    },
    .id   = -1,
};
上面定义是将MFP_PIN_GPIO2这个GPIO口的按键映射到Linux的KEY_F1按键,MPF_PIN_GPIO3映射到KEY_F2,MFP_PIN_GPIO1映射到KEY_F4

(3)上面(2)步实现了从硬件GPIO口到内核标准按键的映射,但是android并没有直接使用映射后的键值,而且对其再进行了一次映射,从内核标准键值
到android所用键值的映射表定义在android文件系统的/system/usr/keylayout目录下。标准的映射文件为qwerty.kl,定义如下:
key 399   GRAVE
key 2     1
key 3     2
key 4     3
key 5     4
key 6     5
key 7     6
key 8     7
key 9     8
key 10    9
key 11    0
key 158   BACK              WAKE_DROPPED
key 230   SOFT_RIGHT        WAKE
key 60    SOFT_RIGHT        WAKE
key 107   ENDCALL           WAKE_DROPPED
key 62    ENDCALL           WAKE_DROPPED
key 229   MENU              WAKE_DROPPED
key 139   MENU              WAKE_DROPPED
key 59    MENU              WAKE_DROPPED
key 127   SEARCH            WAKE_DROPPED
key 217   SEARCH            WAKE_DROPPED
key 228   POUND
key 227   STAR
key 231   CALL              WAKE_DROPPED
key 61    CALL              WAKE_DROPPED
key 232   DPAD_CENTER       WAKE_DROPPED
key 108   DPAD_DOWN         WAKE_DROPPED
key 103   DPAD_UP           WAKE_DROPPED
key 102   HOME              WAKE
key 105   DPAD_LEFT         WAKE_DROPPED
key 106   DPAD_RIGHT        WAKE_DROPPED
key 115   VOLUME_UP
key 114   VOLUME_DOWN
key 116   POWER             WAKE
key 212   CAMERA

key 16    Q
key 17    W
key 18    E
key 19    R
key 20    T
key 21    Y
key 22    U
key 23    I
key 24    O
key 25    P
key 26    LEFT_BRACKET
key 27    RIGHT_BRACKET
key 43    BACKSLASH

key 30    A
key 31    S
key 32    D
key 33    F
key 34    G
key 35    H
key 36    J
key 37    K
key 38    L
key 39    SEMICOLON
key 40    APOSTROPHE
key 14    DEL
       
key 44    Z
key 45    X
key 46    C
key 47    V
key 48    B
key 49    N
key 50    M
key 51    COMMA
key 52    PERIOD
key 53    SLASH
key 28    ENTER
       
key 56    ALT_LEFT
key 100   ALT_RIGHT
key 42    SHIFT_LEFT
key 54    SHIFT_RIGHT
key 15    TAB
key 57    SPACE
key 150   EXPLORER
key 155   ENVELOPE       

key 12    MINUS
key 13    EQUALS
key 215   AT

(4)android对底层按键的处理方法
android按键的处理是Window Manager负责,主要的映射转换实现在android源代码frameworks/base/libs/ui/EventHub.cpp
此文件处理来自底层的所有输入事件,并根据来源对事件进行分类处理,对于按键事件,处理过程如下:
(a)记录驱动名称为
(b)获取环境变量ANDROID_ROOT为系统路径(默认是/system,定义在android源代码/system/core/rootdir/init.rc文件中)
(c)查找路径为"系统路径/usr/keylayout/驱动名称.kl"的按键映射文件,如果不存在则默认用路径为"系统路径/usr/keylayout/qwerty.kl"
这个默认的按键映射文件,映射完成后再把经映射得到的android按键码值发给上层应用程序。
所以我们可以在内核中定义多个按键设备,然后为每个设备设定不同的按键映射文件,不定义则会默认用qwerty.kl

(5)举例
上面(2)步我们在内核中声明了一个名为"gpio-keys"的按键设备,此设备定义在内核drivers/input/keyboard/gpio_keys.c文件中
然后我们在内核启动过程中注册此设备:  platform_device_register(&gpio_keys);
然后我们可以自己定义一个名为gpio-keys.kl的android按键映射文件,此文件的定义可以参考querty.kl的内容,比如说我们想将MPF_PIN_GPIO3
对应的按键作android中的MENU键用,首先我们在内核中将MPF_PIN_GPIO3映射到KEY_F2,在内核include/linux/input.h中查找KEY_F2发现
#define KEY_F2            60
参照KEY_F2的值我们在gpio-keys.kl中加入如下映射即可
key 60    MENU              WAKE
其它按键也照此添加,完成后将按键表放置到/system/usr/keylayout目录下即可。

补充:
(1)android按键设备的映射关系可以在logcat开机日志中找的到(查找EventHub即可)
(2)android按键设备由Window Manager负责,Window Manager从按键驱动读取内核按键码,然后将内核按键码转换成android按键码,转换完成后Window Manager会将内核按键码和android按键码一起发给应用程序来使用,这一点一定要注意。
对于按键事件,调用mDevices[i]->layoutMap->map进行映射。映射实际是由 KeyLayoutMap::map完成的,KeyLayoutMap类里读取配置文件qwerty.kl,由配置 文件 qwerty.kl 决定键值的映射关系。你可以通过修 改./development/emulator/keymaps/qwerty.kl来改变键值的映射关系。 
 JNI 函数 

 在frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp文 件中,向 JAVA提供了函数android_server_KeyInputQueue_readEvent,用于读 取输入设备事件。 

 
 
   C代码 
  
 
 
1. static jboolean   
2. android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,   
3.                                            jobject event)   
4. {   
5.      gLock.lock();   
6.      sp hub = gHub;   
7.      if (hub == NULL) {   
8.           hub = new EventHub;   
9.          gHub = hub;   
10.      }   
11.      gLock.unlock();   
12. 
13.      int32_t deviceId;   
14.      int32_t type;   
15.      int32_t scancode, keycode;   
16.      uint32_t flags;   
17.      int32_t value;   
18.      nsecs_t when;   
19.      bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,   
20.              &flags, &value, &when);   
21. 
22.      env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);   
23.      env->SetIntField(event, gInputOffsets.mType, (jint)type);   
24.      env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);   
25.      env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);   
26.      env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);   
27.      env->SetIntField(event, gInputOffsets.mValue, value);   
28.      env->SetLongField(event, gInputOffsets.mWhen,   
29.                          (jlong)(nanoseconds_to_milliseconds(when)));   
30. 
31.      return res;   
32. }  
 

 readEvent调用hub->getEvent读了取事件,然后转换成JAVA的结构。 
 事件中转线程 

 在frameworks/base/services/java/com/android/server/KeyInputQueue.java 里创建了一个线程,它循环的读取事件,然后把事件放入事件队列里。 

 
 
   Java代码 
  
 
 
1. Thread mThread = new Thread("InputDeviceReader") {   
2.          public void run() {   
3.              android.os.Process.setThreadPriority(   
4.                      android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);   
5. 
6.              try {   
7.                   RawInputEvent ev = new RawInputEvent();   
8.                  while (true) {   
9.                      InputDevice di;   
10. 
11.                      readEvent(ev);   
12. 
13.                      send = preprocessEvent(di, ev);   
14.                      addLocked(di, curTime, ev.flags, ..., me);   
15.                  }   
16.          }   
17.      };  
 

 输入事件分发线程 

 在frameworks/base/services/java/com/android/server/WindowManagerService.java里创建了一个输入事件分发线程,它负责把事件分发到相应的窗口上去。 

 
 
   Java代码 
  
 
 
1. mQueue.getEvent   
2. dispatchKey/dispatchPointer/dispatchTrackball  
 



按键,触摸屏流程分析 

 按键触摸屏流程分析: 
 WindowManagerService类的构造函数 
 WindowManagerService() 
 mQueue = new KeyQ(); 
 因为 WindowManagerService.java (frameworks\base\services\java\com\android\server)中有:    
 private class KeyQ extends KeyInputQueue 
 KeyQ 是抽象类 KeyInputQueue 的实现,所以 new KeyQ类的时候实际上在 KeyInputQueue 类中创建了 
 一个线程 InputDeviceReader 专门用来冲设备读取按键事件,代码: 
 Thread mThread = new Thread("InputDeviceReader") { 
 public void run() 
 { 
         在循环中调用:readEvent(ev); 
     ... 
     send = preprocessEvent(di, ev); 
         实际调用的是 KeyQ 类的 preprocessEvent 函数 
     ... 
     int keycode = rotateKeyCodeLocked(ev.keycode); 
       int[] map = mKeyRotationMap; 
       for (int i=0; i<N; i+=2) 
       { 
         if (map[i] == keyCode) 
           return map[i+1]; 
       } // 
     addLocked(di, curTime, ev.flags,RawInputEvent.CLASS_KEYBOARD,newKeyEvent(di, di.mDownTime, curTime, down,keycode, 0, scancode,...)); 
       QueuedEvent ev = obtainLocked(device, when, flags, classType, event); 
 } 
 } 

 readEvent() 实际上调用的是 com_android_server_KeyInputQueue.cpp (frameworks\base\services\jni)中的: 
 static jboolean android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,jobject event) 
 bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,&flags, &value, &when); 
 调用的是 EventHub.cpp (frameworks\base\libs\ui)中的: 
 bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, 
         int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, 
         int32_t* outValue, nsecs_t* outWhen) 
 在函数中调用了读设备操作:res = read(mFDs[i].fd, &iev, sizeof(iev)); 


 在构造函数 WindowManagerService()调用 new KeyQ() 以后接着调用了: 
 mInputThread = new InputDispatcherThread();       
 ...     
 mInputThread.start(); 
 来启动一个线程 InputDispatcherThread 
 run() 
 process(); 
     QueuedEvent ev = mQueue.getEvent(...) 
 因为WindowManagerService类中: final KeyQ mQueue; 
 所以实际上 InputDispatcherThread 线程实际上从 KeyQ 的事件队列中读取按键事件。 
 switch (ev.classType) 
 case RawInputEvent.CLASS_KEYBOARD: 
     ... 
     dispatchKey((KeyEvent)ev.event, 0, 0); 
     mQueue.recycleEvent(ev); 
     break; 
 case RawInputEvent.CLASS_TOUCHSCREEN: 
     //Log.i(TAG, "Read next event " + ev); 
     dispatchPointer(ev, (MotionEvent)ev.event, 0, 0); 
     break; 

 =============================================================== 


 KeyInputQueue.java (frameworks\base\services\java\com\android\server): 
 的线程 Thread mThread = new Thread("InputDeviceReader") 本地调用: 
 readEvent(ev);读取按键。readEvent 调用的是文件: 
 com_android_server_KeyInputQueue.cpp (frameworks\base\services\jni)中的函数: 
 static jboolean android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz, 
                                           jobject event) 
 android_server_KeyInputQueue_readEvent中有: 
 hub = new EventHub; 
 bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode, 
             &flags, &value, &when); 

 hub->getEvent 调用的是 
 EventHub.cpp (frameworks\base\libs\ui) 文件中的函数: 
 bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, 
         int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, 
         int32_t* outValue, nsecs_t* outWhen) 
 读取按键。 

 class RefBase::weakref_impl : public RefBase::weakref_type 



 在系统启动后,android 会通过 
 static const char *device_path = "/dev/input"; 
 bool EventHub::openPlatformInput(void) 
 res = scan_dir(device_path); 


 通过下面的函数打开设备。 
 int EventHub::open_device(const char *deviceName) 
 { 
 ... 
 fd = open(deviceName, O_RDWR); 
 ... 
 mFDs[mFDCount].fd = fd; 
 mFDs[mFDCount].events = POLLIN; 
 ... 
 ioctl(mFDs[mFDCount].fd, EVIOCGNAME(sizeof(devname)-1), devname); 
 ... 
 const char* root = getenv("ANDROID_ROOT"); 
 snprintf(keylayoutFilename, sizeof(keylayoutFilename), 
                  "%s/usr/keylayout/%s.kl", root, tmpfn); 
 ... 
 device->layoutMap->load(keylayoutFilename); 
 ... 
 } 
 打开设备的时候,如果 device->classes&CLASS_KEYBOARD 不等于 0 表明是键盘。 
 常用输入设备的定义有: 
 enum { 
         CLASS_KEYBOARD      = 0x00000001, //键盘 
         CLASS_ALPHAKEY      = 0x00000002, // 
         CLASS_TOUCHSCREEN   = 0x00000004, //触摸屏 
         CLASS_TRACKBALL     = 0x00000008 //轨迹球 
     }; 
 打开键盘设备的时候通过上面的 ioctl 获得设备名称,命令字 EVIOCGNAME 的定义在文件: 
 kernel/include/linux/input.h 中。 
 #define EVIOCGNAME(len)   _IOC(_IOC_READ, 'E', 0x06, len) /* get device name */ 
 在内核键盘驱动文件 drivers/input/keyboard/pxa27x_keypad.c 中定义了设备名称:pxa27x-keypad 
 static struct platform_driver pxa27x_keypad_driver = { 
     .probe        = pxa27x_keypad_probe, 
     .remove        = __devexit_p(pxa27x_keypad_remove), 
     .suspend    = pxa27x_keypad_suspend, 
     .resume        = pxa27x_keypad_resume, 
     .driver        = { 
         .name    = "pxa27x-keypad", 
         .owner    = THIS_MODULE, 
     }, 
 }; 
 ANDROID_ROOT 为环境变量,在android的命令模式下通过 printenv 可以知道它为: system 
 所以 keylayoutFilename 为:/system/usr/keylayout/pxa27x-keypad.kl 
 pxa27x-keypad.kl 定义了按键映射,具体内容如下: 
 ---------------------- 
 # NUMERIC KEYS 3x4 
 key 2   1 
 key 3   2 
 key 4   3 
 key 5   4 
 key 6   5 
 key 7   6 
 key 8   7 
 key 9   8 
 key 10 9 
 key 11 0 
 key 83 POUND 
 key 55 STAR 

 # FUNCTIONAL KEYS 
 key 231 MENU        WAKE_DROPPED 
 key 192 BACK           WAKE_DROPPED 
 key 193 HOME       WAKE 
 key 107 DEL        WAKE 
 key 102 CALL        WAKE_DROPPED 
 key 158 ENDCALL     WAKE_DROPPED 
 key 28   DPAD_CENTER     WAKE 
 key 115 VOLUME_UP 
 key 114 VOLUME_DOWN 
 ---------------------- 
 如果没有定义键盘映射文件,那么默认使用系统的 /system/usr/keylayout/qwerty.kl 
 可以修改 /system/usr/keylayout/qwerty.kl 文件改变Android公司的按键映射。 

 device->layoutMap->load(keylayoutFilename) 调用的是文件: 
 KeyLayoutMap.cpp (frameworks\base\libs\ui)中的函数: 
 status_t KeyLayoutMap::load(const char* filename)通过解析 pxa27x-keypad.kl 
 把按键的映射关系保存在 :KeyedVector<int32_t,Key> m_keys; 中。 
 当获得按键事件以后调用: 
 status_t KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t *flags) 
 由映射关系 KeyedVector<int32_t,Key> m_keys 把扫描码转换成andorid上层可以识别的按键。