5.4 用户输入BSP的结构

移植Android的用户输入系统,主要的工作分成以下两个部分。

·输入(Input)驱动程序

·用户空间中动态配置的kl和kcm文件

Android用户输入部分的硬件适配层就是libui库中的EventHub,这部分是系统标准的部分。因此,在实现特定硬件平台的Android系统的时候,用户输入的硬件抽象层通常情况下不做改变。EventHub使用Linux标准的input设备作为输入设备,其中又以实用Event设备居多。在这种情况下,为了实现Android系统的输入,也必须使用Linux标准Input驱动程序作为标准的输入。

由于标准化程度比较高,实现用户输入系统,在用户空间一般不需要更改代码。唯一的情况是使用不同的kl和kcm文件,使用按键的布局和按键字符映射关系。

↘5.4.1 Input驱动程序

Input驱动程序是Linux输入设备的驱动程序,分成游戏杆(joystick)、鼠标(mouse和mice)和事件设备(Event queue)3种驱动程序。目前以事件驱动程序为主,作为通用的输入驱动,可以支持键盘、鼠标、触摸屏等多种输入设备。

Input驱动程序的主设备号是13,驱动程序的设备号分配如下所示。

·joystick游戏杆:0~31

·mouse鼠标:32~62

·mice鼠标:63

·事件(Event)设备:64~95

实际上,每一种Input设备占用5位,因此每种设备包含的个数是32个。

Event设备在用户空间大多使用read、ioctl、poll等文件系统的接口进行操作,read用于读取输入信息,ioctl用于获得和设置信息,poll调用可以进行用户空间的阻塞。当内核有按键等中断时,通过在中断中唤醒poll的内核实现,这样在用户空间的poll调用也可以返回。

Event设备在文件系统中的设备节点为:/dev/input/eventX。主设备号为13,次设备号递增生成,为64~95,各个具体的设备在misc、touchscreen、keyboard等目录中。

输入设备驱动程序的头文件:include/linux/input.h。

输入设备驱动程序的核心和Event代码分别是:drivers/input/input.c和drivers/input/evdev.c。Event输入驱动的架构如图5-3所示。

图5-3 Event设备驱动的架构

input.h中定义了input_dev结构,它表示Input驱动程序的各种信息,对于Event设备分为同步设备、键盘、相对设备(鼠标)、绝对设备(触摸屏)等。

input_dev中定义并归纳了各种设备的信息,例如按键、相对设备、绝对设备、杂项设备、LED、声音设备、强制反馈设备、开关设备等。

    struct input_dev {
      const char *name;                                      // 设备名称
      const char *phys;                                      // 设备在系统的物理路径
      const char *uniq;                                      // 统一的ID
      struct input_id id;                                     // 设备ID
      unsigned long evbit[BITS_TO_LONGS(EV_CNT)];             // 事件
      unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];           // 按键
      unsigned long relbit[BITS_TO_LONGS(REL_CNT)];           // 相对设备
      unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];           // 绝对设备
      unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];           // 杂项设备
      unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];           // LED
      unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];           // 声音设备
      unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];             // 强制反馈设备
      unsigned long swbit[BITS_TO_LONGS(SW_CNT)];             // 开关设备
      unsigned int keycodemax;                                // 按键码的最大值
      unsigned int keycodesize;                               // 按键码的大小
      void *keycode;                                         // 按键码
      int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
      int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);
      struct ff_device *ff;
      unsigned int repeat_key;
      struct timer_list timer;
      int sync;
      int abs[ABS_MAX + l];
      int rep[REP_MAX + l];
      unsigned long key[BITS_TO_LONGS(KEY_CNT)];
      unsigned long led[BITS_TO_LONGS(LED_CNT)];
      unsigned long snd[BITS_TO_LONGS(SND_CNT)];
      unsigned long sw[BITS_TO_LONGS(SW_CNT)];
      int absmax[ABS_MAX + l];                                // 绝对设备相关内容
      int absmin[ABS_MAX + l];
      int absfuzz[ABS_MAX + l];
      int absflat[ABS_MAX + l];
      int (*open)(struct input_dev *dev);                   // Input设备相关的操作的函数指针
      void (*close)(struct input_dev *dev);
      int (*flush)(struct input_dev *dev, struct file *file);
      int (*event)(struct input_dev *dev, unsigned int type,
                  unsigned int code, int value);
      struct input_handle *grab;
      spinlock_t event_lock;
      struct mutex mutex;
      unsigned int users;
      int going_away;
      struct device dev;                                      // 表示设备的结构和链表
      struct list_head  h_list;
      struct list_head  node;
    };

在具体的Event驱动程序的实现中,如果得到按键的事件,通常需要通过以下的接口向上进行通知,这些内容也在input.h中定义,如下所示:

    void input_event(struct input_dev *dev, unsigned int type,
                        unsigned int code, int value);
    void input_inject_event(struct input_handle *handle,
                        unsigned int type, unsigned int code, int value);
    static inline void input_report_key(struct input_dev *dev,
                        unsigned int code, int value)
    {     input_event(dev, EV_KEY, code, !!value); }
    static inline void input_report_rel(struct input_dev *dev,
                        unsigned int code, int value)
    {input_event(dev, EV_REL, code, value); }
    static inline void input_report_abs(struct input_dev *dev,
                        unsigned int code, int value)
    {input_event(dev, EV_ABS, code, value); }
    static inline void input_report_ff_status(struct input_dev *dev,
                        unsigned int code, int value)
    {input_event(dev, EV_FF_STATUS, code, value); }
    static inline void input_report_switch(struct input_dev *dev,
                        unsigned int code, int value)
    {input_event(dev, EV_SW, code, !!value); }
    static inline void input_sync(struct input_dev *dev)
    {input_event(dev, EV_SYN, SYN_REPORT, 0); }

对不同设备内容的报告均是通过input_event()函数来完成的,但使用了不同参数。

在手机系统中经常使用的键盘(keyboard)和小键盘(kaypad)属于按键设备EV_KEY,轨迹球属于相对设备EV_REL,触摸屏属于绝对设备EV_ABS。

关于按键数值的定义片段如下所示:

    #define KEY_RESERVED    0
    #define KEY_ESC         l
    #define KEY_l           2
    #define KEY_2           3
    #define KEY_0           ll
    #define KEY_MINUS       l2
    #define KEY_EQUAL       l3
    #define KEY_BACKSPACE   l4
    #define KEY_TAB         l5
    #define KEY_Q           l6
    #define KEY_W           l7
    #define KEY_E           l8
    #define KEY_R           l9
    #define KEY_T           20

这里用整数标识各个按键,这种数值通常又被称为“扫描码”。

getevent命令可以对Event设备进行调试,执行命令行,所有使用Input设备的输入情况将打出信息。在Android的模拟器环境中,使用getevent的情况如下所示:

    # getevent
    add device l: /dev/input/event0
      name:    "qwerty2"
    could not get driver version for /dev/input/mouse0, Not a typewriter
    could not get driver version for /dev/input/mice, Not a typewriter
    /dev/input/event0: 000l 00020000000l
    /dev/input/event0: 000l 000200000000

点击数字按键1,出现了上面的信息,0002是按键的扫描码,00000001和00000000分别是按下和抬起的附加信息。最前面的0001实际上是输入设备的类型。

使用getevent()可以最直接地获得按键的扫描码,从硬件的源头确定底层输入设备传递上来的信息。

↘5.4.2 输入配置文件

Android输入系统在用户空间的内容,可以通过配置文件进行配置。

·kl(Keycode Layout)文件:后缀名为kl的配置文件。

·kcm(KeyCharacterMap)文件:后缀名为kcm的配置文件。

系统默认配置文件的路径为:sdk/emulator/keymaps/。

经过处理后,kl和kcm文件被分别放置在目标文件系统的/system/usr/keylayout/目录和/system/usr/keychars/目录中。kl文件将被直接复制到目标文件系统中;由于尺寸较大,kcm文件放置在目标文件系统之前,需要经过压缩处理。

kl和kcm的文件名的含义为<设备>.kl和<设备>.kcm。其中<设备>的含义是输入设备的设备名,对于某一个输入设备,如果具有相应设备名的kl或kcm文件,将优先使用;如果没有,将使用默认的配置文件。

1.kl:按键布局文件

kl文件将被直接放在目标系统的/system/usr/keylayout/目录中。Android默认提供的按键布局文件主要包括qwerty.kl。另一个按键布局文件AVRCP.kl用于多媒体的控制,ACRCP的含义为Audio/Video Remote Control Profile。

qwerty.kl为全键盘的布局文件,是系统中主要按键使用的布局文件,也是被默认使用的,其片段如下所示:

    key 399   GRAVE
    key 2     l
    key 3     2
    key 4     3
    key 5     4
    key 6     5
    key 7     6
    key 8     7
    key 9     8
    key l0    9
    key ll    0
    key l58   BACK             WAKE_DROPPED
    key 230   SOFT_RIGHT       WAKE
    key 60    SOFT_RIGHT       WAKE
    key l07   ENDCALL          WAKE_DROPPED
    key 62    ENDCALL          WAKE_DROPPED
    key 229   MENU             WAKE_DROPPED
    # 省略部分按键的对应内容
    key l6    Q
    key l7    W
    key l8    E
    key l9    R
    key 20    T
    key ll5   VOLUME_UP
    key ll4   VOLUME_DOWN

在按键布局文件中,第1列为按键的扫描码,是一个整数值;第2列为按键的标签,是一个字符串,完成了按键信息的第1次转化,将整型的扫描码转换成字符串类型的按键标签。第3列表示按键的Flag,带有WAKE字符,表示这个按键可以唤醒系统。

扫描码来自驱动程序,显然,不同的扫描码可以对应一个按键标签。表示物理上的两个按键可以对应同一个功能按键。

例如,当驱动程序上传扫描码为158的时候,对应的标签为BACK,再经过第二次转换,根据KeycodeLabels.h的KEYCODES数组,其对应的按键码为4。

按键布局文件同时兼顾了input驱动程序的定义和Android中按键的定义。例如,Input驱动程序中定义的数字扫描码KEY_1的数值为2,这里2对应的按键标签为“1”;Input驱动程序中定义字母扫描码KEY_Q的数值为16,这里对应的按键标签为“Q”。然而各种Android设备中使用到的全键盘毕竟有不同之处,因此有一些按键与input驱动程序的定义没有对应关系。

2.kcm:按键字符映射文件

kcm表示按键字符的映射关系,主要功能是将整数类型按键码(keycode)转换成可以显示的字符。kcm文件将被makekcharmap工具转换成二进制的格式,放在目标系统的/system/usr/keychars/目录中。

qwerty.kcm是默认的按键字符映射文件,表示全键盘字符映射,其片段如下所示:

    [type=QWERTY]
    # keycode      display number  base    caps    fn     caps_fn
    A             'A'    '2'    'a'    'A'    '#'    0x00
    B             'B'    '2'    'b'    'B'    '<'    0x00
    C             'C'    '2'    'c'    'C'    '9'    0x00E7
    D             'D'    '3'    'd'    'D'    '5'    0x00
    E             'E'    '3'    'e'    'E'    '2'    0x030l
    F             'F'    '3'    'f'    'F'    '6'    0x00A5
    G             'G'    '4'    'g'    'G'    '-'    '_'
    H             'H'    '4'    'h'    'H'    '['    '{'
    I             'I'    '4'    'i'    'I'    '$'    0x0302
    J             'J'    '5'    'j'    'J'    ']'    '}'
    K             'K'    '5'    'k'    'K'    '"'    '~'
    L             'L'    '5'    'l'    'L'    '''    '`'
    M             'M'    '6'    'm'    'M'    '!'    0x00
    N             'N'    '6'    'n'    'N'    '>'    0x0303

第一列是转换之前的按键码,第二列之后分别表示转换成为的显示内容(display)、数字(number)等。这些转化的内容和KeyCharacterMap.h中定义的getDisplayLabel()、getNumber()等函数相对应。

这里的类型,除了QWERTY之外,还可以是Q14(单键多字符对应的键盘)、NUMERIC(12键的数字键盘)等。