Android 输入设备配置介绍

1. 概述

1.1. Android 输入管道

当按键按压和轻触接触点时物理输入设备会生成描述状态更改的信号。这个时候设备固件以某种方式编码和传输这些信号,例如向系统发送 USB HID 报告或在 I2C 总线上产生中断。

然后,信号由 Linux 内核中的设备驱动程序解码。Linux 内核为许多标准的外围设备提供驱动程序,特别是那些符合 HID 协议的外围设备。然而,原始设备制造商 (OEM) 通常必须为系统中的嵌入式设备(如触摸屏)提供自定义驱动程序。

输入设备驱动 程序负责通过 Linux 输入协议将设备特定信号转换为标准输入事件格式。Linux 输入协议在 linux/input.h 内核头文件中定义了一组标准事件类型和代码。这样一来,内核之外的组件就不需要关注物理扫描代码、HID 用途、I2C 消息、GPIO 引脚等方面的详细信息。

接下来,Android EventHub 组件通过打开与每个输入设备关联的 evdev 驱动程序从内核读取输入事件。然后,Android InputReader 组件根据设备类别解码输入事件,并生成 Android 输入事件流。在此过程中,Linux 输入协议事件代码将根据输入设备配置、键盘布局文件和各种映射表,转化为 Android 事件代码。

最后,InputReader 将输入事件发送到 InputDispatcher,由其将这些事件转发到相应的窗口。

以红外遥控器为例,整个 Android 输入管道

1

1.2. 控制点

在输入管道中有几个阶段会影响对输入设备行为的控制。

1.2.1. 驱动程序和固件配置

输入设备驱动程序经常通过在寄存器中设置参数甚或上传固件本身来配置输入设备的行为。对于诸如触摸屏等嵌入式设备尤其如此,其中一大部分校准过程涉及到调整这些参数或修复固件,以提供所需的精度和响应能力并抑制噪声。

驱动程序配置选项通常在内核板级支持包 (BSP) 中指定为模块参数,以便同一驱动程序可以支持多种不同的硬件实现。

1.2.2. 板配置属性

内核板级支持包 (BSP) 可以通过由 Android InputReader 组件使用的 SysFS 导出板配置属性,如虚拟键在触摸屏上的位置。

1.2.3. 资源叠加层

有几个输入行为是通过 config.xml 中的资源叠加层配置的,例如机盖开关的操作。

以下是几个配置例子:

  • config_lidKeyboardAccessibility:指定机盖开关对硬件键盘可访问还是隐藏。
  • config_lidNavigationAccessibility:指定机盖开关对触控板可访问还是隐藏。
  • config_longPressOnPowerBehavior:指定当用户按住电源按钮时应当发生的行为。
  • config_lidOpenRotation:指定机盖开关对屏幕方向的影响。

如需详细了解每个配置选项,可以参考 frameworks/base/core/res/res/values/config.xml 中的文档。

1.2.4. 按键映射

Android EventHub 和 InputReader 组件使用按键映射来配置按键、操纵杆按钮和操纵杆轴从 Linux 按键代码Android 按键代码 的映射。映射可能会因设备或语言的不同而不同。

1.2.5. 输入设备配置文件

Android EventHub 和 InputReader 组件使用输入设备配置文件来配置特殊设备特征,例如,如何报告触摸尺寸信息。

2. 按键布局文件

按键布局文件(.kl 文件)将 Linux 按键代码轴代码 映射到 Android 按键代码 和轴代码,并指定关联的政策标志。对于设备专属按键布局文件而言应该考虑以下两点:

  • 对具有按键(包括音量、电源和耳机媒体按键等特殊按键)的内部(内置)输入设备而言是必要文件。
  • 对其他输入设备而言是可选文件,而对特殊用途的键盘和操纵杆而言则是推荐文件。 如果没有可用的设备专属按键布局文件,则系统将改选默认文件。

2.1. 配置文件目录

按键布局文件由供应商、产品(可能还包括版本)ID 或输入设备名称来确定位置。系统会按顺序检索以下路径:

  • /odm/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
  • /vendor/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
  • /system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
  • /data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl
  • /odm/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
  • /vendor/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
  • /system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl
  • /data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl
  • /odm/usr/keylayout/DEVICE_NAME.kl
  • /vendor/usr/keylayout/DEVICE_NAME.kl
  • /system/usr/keylayout/DEVICE_NAME.kl
  • /data/system/devices/keylayout/DEVICE_NAME.kl
  • /odm/usr/keylayout/Generic.kl
  • /vendor/usr/keylayout/Generic.kl
  • /system/usr/keylayout/Generic.kl
  • /data/system/devices/keylayout/Generic.kl

注意: 当构建包含设备名称的文件路径时,设备名称中除“0-9”、“a-z”、“A-Z”、“-”或 “_” 之外的所有字符将替换为“_”。

2.1.1. 检索优先级

对于相同目录下,检索优先级为会优先匹配 Vendor id, Product id 和 version id 共同组合形成的 Vendor_XXXX_Product_XXXX_Version_XXXX.kl 文件,之后再匹配不带 version id 的 Vendor_XXXX_Product_XXXX.kl 文件,最后匹配以设备名字命名的 DEVICE_NAME.kl 文件。如果以上文件都没有情况下则会使用默认文件 Generic.kl,其中 /system/usr/keylayoutGeneric.kl 文件为系统预置的对于所有输入设备的默认配置文件,不可以随意更改。

对于不同的目录,检索优先级为 /odm/usr/keylayout > /vendor/usr/keylayout > /system/usr/keylayout > /data/system/devices/keylayout

2.2. 语法

按键布局文件是由按键或坐标轴声明和标记组成的纯文本文件。

2.2.1. 按键声明

按键声明包含关键字 key(后跟一个 Linux 按键代码编号和 Android 按键代码名称),或该关键字的用途(后跟 HID 用途和 Android 按键代码名称)。HID 用途以 32 位整数表示,其中高 16 位表示 HID 用途页面,低 16 位表示 HID 用途 ID。任何一项声明都可以后跟一组由空格分隔的可选政策标志。

key 1     ESCAPE
key 114   VOLUME_DOWN
key 16    Q                 VIRTUAL
key usage 0x0c006F          BRIGHTNESS_UP

可识别以下政策标志:

  • FUNCTION:按键应解读为如同也按下了 FUNCTION 键。
  • GESTURE:按键由用户手势(例如手掌摸触摸屏)生成。
  • VIRTUAL:按键是与主触摸屏相邻的虚拟软键(电容式按钮)。这会导致启用特殊的去抖动逻辑。

2.2.2. 坐标轴声明

每个坐标轴声明都包含关键字 axis,后跟一个 Linux 坐标轴代码编号和限定符,用于控制坐标轴(包括至少一个 Android 坐标轴代码名称)的行为。

2.2.2.1. 基本坐标轴

基本坐标轴仅会将 Linux 坐标轴代码映射到 Android 坐标轴代码名称。以下声明将 ABS_X(由 0x00 表示)映射到 AXIS_X(由 X 表示)。

axis 0x00 X

在上述示例中,如果 ABS_X 的值为 5,那么 AXIS_X 会设为 5。

2.2.2.2. 分轴

分轴将一个 Linux 坐标轴代码映射到两个 Android 坐标轴代码名称,以便小于或大于阈值的值在映射时可以分割到两个不同的坐标轴。当设备报告的单个物理轴对两个不同的互斥逻辑轴进行编码时,此映射非常有用。

当 ABS_Y 轴的值(由 0x01 表示)小于 0x7f 时,以下声明会将其映射到 AXIS_GAS;当该值大于 0x7f 时,会将其映射到 AXIS_BRAKE。

axis 0x01 split 0x7f GAS BRAKE

在上述示例中,如果 ABS_Y 的值为 0x7d,那么 AXIS_GAS 设为 2 (0x7f - 0x7d),并且 AXIS_BRAKE 设为 0。相反,如果 ABS_Y 的值为 0x83,那么 AXIS_GAS 设为 0,并且 AXIS_BRAKE 设为 4 (0x83 - 0x7f)。最后,如果 ABS_Y 的值等于 0x7f 的分值,那么 AXIS_GAS 和 AXIS_BRAKE 均设为 0。

2.2.2.3. 反转坐标轴

反转坐标轴会反转坐标轴值的符号。以下声明将 ABS_RZ(由 0x05 表示)映射到 AXIS_BRAKE(由 BRAKE 表示),并通过求补来反转输出。

axis 0x05 invert BRAKE

在上述示例中,如果 ABS_RZ 的值为 2,那么 AXIS_BRAKE 设为 -2。

2.2.2.4. 中心平面选项

由于存在噪声,例如在操纵杆未被使用时,操纵杆设备也可能会报告输入事件。这种噪声通常来自左侧杆和/或右侧杆,会导致驱动程序报告接近 0 的位置值。“中心平面” 值用于指定预期会从处于静止状态的控制杆获得的噪声量。

Linux 输入协议为输入设备驱动程序提供了一种用于指定操纵杆轴中心平面值的方式,但并非所有驱动程序都会报告该值,并且有些驱动程序会提供不正确的值。为了解决这个问题,可以在轴声明后面加一个 flat 选项,用于指定轴的中心位置周围多宽的区域应被视为中心区域。

例如,如果设备驱动程序报告的 AXIS_X 值介于 0 到 100 之间,且 Android 输入系统会将 0 映射到 -1,并将 100 映射到 1。该范围的中心在未缩放的坐标中为 50,在经过缩放的坐标中为 0。如果平面值等于 10,那么开发者应假定报告的任何介于 -0.1 到 0.1 之间的 AXIS_X 值(在未缩放的坐标中,则是介于 40 到 60 之间)均为噪声,并将来自操纵杆的这些值视为零。

axis 0x03 Z flat 4096

在以上示例中,中心平面值设为了 4096。

2.3. 示例

2.3.1. 键盘
key 1     ESCAPE
key 2     1
key 3     2
key 10    9
key 11    0
key 12    MINUS
key 13    EQUALS
key 14    DEL
2.3.2. 系统控件
key 114   VOLUME_DOWN
key 115   VOLUME_UP
key 116   POWER
2.3.3. 电容式按钮
key 139    MENU           VIRTUAL
key 172    HOME           VIRTUAL
key 158    BACK           VIRTUAL
key 217    SEARCH         VIRTUAL
2.3.4. 操纵杆
key 304   BUTTON_A
key 305   BUTTON_B
key 307   BUTTON_X
key 308   BUTTON_Y
key 310   BUTTON_L1
key 311   BUTTON_R1
key 314   BUTTON_SELECT

3. 按键字符映射文件

按键字符映射文件(.kcm 文件)负责将 Android 按键代码与辅助键的组合映射到 Unicode 字符。

如果没有提供特定于设备的按键布局文件,系统将选择默认按键布局文件。

由于 按键字符映射文件 通常不需要配置,这里不做具体介绍,有兴趣的可以参阅官方文档

https://source.android.google.cn/docs/core/interaction/input/key-character-map-files

4. 输入设备配置文件

输入设备配置文件(.idc 文件)包含设备专用配置属性,这些属性会影响输入设备的行为。

输入设备配置文件的基本原理是根据 Linux 内核输入设备驱动程序报告的事件类型和属性,Android 会自动检测和配置大多数输入设备功能。

例如,如果输入设备支持 EV_REL 事件类型、代码 REL_X 和 REL_Y 以及 EV_KEY 事件类型和 BTN_MOUSE,那么 Android 会将输入设备归类为鼠标。鼠标的默认行为是在屏幕上显示光标,光标跟踪鼠标的移动并在鼠标被点击时模拟触摸操作。虽然可以通过不同的方式配置鼠标,但是默认行为通常足以用于标准的鼠标外围设备。

由于 输入设备配置文件 通常可以根据 Linux 内核输入设备驱动程序报告的事件类型和属性自动检测和配置,因此很少需要厂商单独配置,这里不做具体介绍,有兴趣的可以参阅官方文档。

https://source.android.google.cn/docs/core/interaction/input/input-device-configuration-files

5. sstar 配置方式

对于公版而言,以 按键布局文件 为例,需要在设备端的 device.mk (公版 device.mk 文件一般位于 device/sigmastar/<device-name>/<product-name>)中做如下修改配置:

PRODUCT_COPY_FILES += \
device/sigmastar/common/input/Vendor_8888_Product_00ff.kl:$(TARGET_COPY_OUT_VENDOR)/usr/keylayout/Vendor_8888_Product_00ff.kl

其中 Vendor_8888_Product_00ff.kl 是公版对应的输入设备 按键布局文件vendor id 为8888,product id 为00ff),不同的输入设备有所差异,sstar 公版支持的输入设备的配置文件都会统一放置在 device/sigmastar/common/input/ 路径下,以便在不同的设备之间共享相同输入输入设备配置。在这里,sstar 公版支持的配置文件会拷贝到 vendor/usr/keylayout/ 目录,这个目录也是如前文所说的 按键布局文件 检索目录。

对于 odm 厂商而言,同样需要在设备端的 device.mk 中做如下修改配置:

PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/input/Vendor_8888_Product_00ff.kl:$(TARGET_COPY_OUT_ODM)/usr/keylayout/Vendor_8888_Product_00ff.kl

其中 Vendor_8888_Product_00ff.klodm 各自的输入设备 按键布局文件,不同的输入设备有所差异,odm 的配置文件建议放置在 device.mk 所在路径下的 input 文件夹。在这里,odm 支持的配置文件会拷贝到 odm/usr/keylayout/ 目录,该目录优先级会高于 vendor 目录的同名配置文件(配置文件检索目录优先级在前文中有介绍)。

对于不同的配置文件名中的 vendor 以及 product id 可以使用 getevent 指令获取,在串口或者使用 adb 指令输入 getevent -i 即可获取所有输入设备相关信息:

console:/ # getevent -i
add device 1: /dev/input/event1
bus:      0018
vendor    8888 // vendor id
product   00ff // product id
version   0001 // version id
name:     "sstar ir" // device name
location: "/dev/ir"
id:       ""
version:  1.0.1
events:
    KEY (0001): 001c  003b  003c  003d  003e  003f  0040  0066 // 支持上报的 KEY 值
                0067  0069  006a  006c  0071  0072  0073  0074
                008b  009e
    REL (0002): 0000  0001
    MSC (0004): 0004
input props:
    INPUT_PROP_POINTING_STICK
could not get driver version for /dev/input/mouse0, Inappropriate ioctl for device
could not get driver version for /dev/input/mice, Inappropriate ioctl for device
add device 2: /dev/input/event0
bus:      0018
vendor    8888 // vendor id
product   807f // product id
version   1020
name:     "Goodix Capacitive TouchScreen"
location: "input/ts"
id:       ""
version:  1.0.1
events:
    KEY (0001): 003b  003c  003d  003e  003f  0040  007d  014a
    ABS (0003): 0000  : value 172, min 0, max 799, fuzz 0, flat 0, resolution 0
                0001  : value 307, min 0, max 479, fuzz 0, flat 0, resolution 0
                002f  : value 0, min 0, max 4, fuzz 0, flat 0, resolution 0
                0030  : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
                0032  : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
                0035  : value 0, min 0, max 799, fuzz 0, flat 0, resolution 0
                0036  : value 0, min 0, max 479, fuzz 0, flat 0, resolution 0
                0039  : value 0, min 0, max 65535, fuzz 0, flat 0, resolution 0
input props:
    INPUT_PROP_DIRECT

从输出的结果中很容易就可以获取到 vendorproductversion id 等命名配置文件必要的信息。当然,配置文件名字有多种命名方式,只要能够正确匹配到输入设备即可,详情请参阅前文。

6. 按键布局文件编写参考

对于输入设备而言,最重要的是配置 按键布局文件,对于 按键字符映射文件 可以使用原生默认的文件,同时 输入设备配置文件AndroidInputReader 流程中可以通过输入设备的 props 属性动态获取必要的配置,因此也可以不配置。

6.1. Linux 按键代码和 Android 按键代码

对于配置 按键布局文件 而言,首先需要知道 LinuxAndroid 的按键代码有哪些,以下文件中列出了 Linux 的所有按键代码:

inclue/uapi/linux/input-event-codes.h

 /*
   * Device properties and quirks
   */

  #define INPUT_PROP_POINTER        0x00    /* needs a pointer */
  #define INPUT_PROP_DIRECT        0x01    /* direct input devices */
  #define INPUT_PROP_BUTTONPAD        0x02    /* has button(s) under pad */
  #define INPUT_PROP_SEMI_MT        0x03    /* touch rectangle only */
  #define INPUT_PROP_TOPBUTTONPAD        0x04    /* softbuttons at top of pad */
  #define INPUT_PROP_POINTING_STICK    0x05    /* is a pointing stick */
  #define INPUT_PROP_ACCELEROMETER    0x06    /* has accelerometer */

  #define INPUT_PROP_MAX            0x1f
  #define INPUT_PROP_CNT            (INPUT_PROP_MAX + 1)

  /*
   * Event types
   */

  #define EV_SYN            0x00
  #define EV_KEY            0x01
  #define EV_REL            0x02
  #define EV_ABS            0x03
  #define EV_MSC            0x04
  #define EV_SW            0x05
  #define EV_LED            0x11
  #define EV_SND            0x12
  #define EV_REP            0x14
  #define EV_FF            0x15
  #define EV_PWR            0x16
  #define EV_FF_STATUS        0x17
  #define EV_MAX            0x1f
  #define EV_CNT            (EV_MAX+1)

  /*
   * Synchronization events.
   */

  #define SYN_REPORT        0
  #define SYN_CONFIG        1
  #define SYN_MT_REPORT        2
  #define SYN_DROPPED        3
  #define SYN_MAX            0xf
  #define SYN_CNT            (SYN_MAX+1)

  /*
   * Keys and buttons
   *
   * Most of the keys/buttons are modeled after USB HUT 1.12
   * (see http://www.usb.org/developers/hidpage).
   * Abbreviations in the comments:
   * AC - Application Control
   * AL - Application Launch Button
   * SC - System Control
   */

  #define KEY_RESERVED        0
  #define KEY_ESC            1
  #define KEY_1            2
  #define KEY_2            3
  #define KEY_3            4
  #define KEY_4            5
...
  #define KEY_HOMEPAGE        172    /* AC Home */
...

Android 按键代码全部定义在以下文件中:

frameworks/native/include/android/keycodes.h

// keycodes.h
 /**
   * Key codes.
   */
  enum {
      /** Unknown key code. */
      AKEYCODE_UNKNOWN         = 0,
      /** Soft Left key.
       * Usually situated below the display on phones and used as a multi-function
       * feature key for selecting a software defined function shown on the bottom left
       * of the display. */
      AKEYCODE_SOFT_LEFT       = 1,
      /** Soft Right key.
       * Usually situated below the display on phones and used as a multi-function
       * feature key for selecting a software defined function shown on the bottom right
       * of the display. */
      AKEYCODE_SOFT_RIGHT      = 2,
      /** Home key.       * This key is handled by the framework and is never delivered to applications. */
      AKEYCODE_HOME            = 3,
      /** Back key. */
      AKEYCODE_BACK            = 4,
      /** Call key. */
      AKEYCODE_CALL            = 5,
      /** End Call key. */
      AKEYCODE_ENDCALL         = 6,
      /** '0' key. */
      AKEYCODE_0               = 7,
      /** '1' key. */
      AKEYCODE_1               = 8,
      /** '2' key. */
      AKEYCODE_2               = 9,
      /** '3' key. */
      AKEYCODE_3               = 10,
...

在文件中会简单介绍每个按键代码所代表的含义。

6.2. 配置实例

配置 按键布局文件 时可以重点参考 Android 原生默认的按键布局文件 general.kl 以及一些预置的配置文件,文件位于以下路径:

/frameworks/base/data/keyboards/

// /frameworks/base/data/keyboards/ 目录下文件
AVRCP.idc
AVRCP.kl
Android.mk
Generic.kcm
Generic.kl
OWNERS
Vendor_0079_Product_0011.kl
Vendor_0079_Product_18d4.kl
Vendor_044f_Product_b326.kl
Vendor_045e_Product_028e.kl
Vendor_045e_Product_028f.kl

其中 Generic.kl 是当输入设备找不到相应的 按键布局文件 时的默认文件,它基本涵盖了常用的一些输入设备的通用配置,可以重点作为我们配置 按键布局文件 的参考。

// Generic.kl
key 1     ESCAPE
 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 12    MINUS
 key 13    EQUALS
 key 14    DEL
 key 15    TAB
 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 28    ENTER
 key 29    CTRL_LEFT

下例为给vendor 以及 product id 分别为 888800ff 红外遥控器配置按键布局文件:

key 28    KPAD_CENTER
key 399   TV_INPUT
key 189   SETTINGS
...
key 172   HOME
...

kl 配置文件的配置方式和语法参考前文。本例中 172 为红外遥控器按下 HOME 键时上报的 Linux 按键代码(KEY_HOMEPAGE),而 HOME 则为对应的 Android 按键代码(AKEYCODE_HOME)。需要注意的是配置文件中的 Android 按键代码在 keycodes.h 文件中匹配时需要加 AKEYCODE_ 前缀。