安卓遥控最常用的是红外遥控和2.4G无线飞鼠,原理都是差不多的。

空中飞鼠属于USB协议里的HID设备,驱动目录: kernel\drivers\hid\,键值和kernel\drivers\hid\hid-input.c里有个数组有关。

红外遥控发射红外码值比如:0x32,通过kernel里面配置的dtsi文件映射到一个linux code 
然后通过device目录下面的某个kl文件映射到一个具体的android keycode
这样就可以在应用层通过onKeyDown()监听到这个按键:

红外码值 --> linux层 --> android层--> onKeyDown()监听按键

红外码值和linux的映射:<0xbf KEY_BACK>在kernel的dts文件中某一组 ir_key{}里面
0xbf是红外码值
KEY_BACK是linux的keycode,
在文件kernel/include/uapi/linux/input.h 中定义
有的平台可能是别的文件名,大概都是在这个位置。

linux和安卓层的映射文件在device的配置文件,后缀为kl

key 89    RO
key 87    F11

F11是安卓keycode,在下列这些文件中定义:

frameworks/base/core/java/android/view/KeyEvent.java
frameworks/native/include/android/keycodes.h
frameworks/native/include/input/InputEventLabels.h
frameworks/base/core/res/res/values/attrs.xml

假如我们现在接到一个任务需要适配一个新的遥控器,首先我们测试一下遥控的客户码和所有的按键红外码值,具体的测试方法各个方案平台都有对应,通过串口打印信息可以看到。

RK打印红外码值
adb shell环境下输入
echo 1 > /sys/module/rockchip_pwm_remotectl/parameters/code_print
cat /proc/kmsg
按遥控按键就会有码值打印
全志打印红外码值
echo 0xff > /sys/module/sunxi_ir_rx/parameters/debug_mask

将红外码配置到对应的dtsi文件里面:

ir_key3 {
 usercode = <0x1dcc>;
 key_table =
 <0xee KEY_REPLY>,
 <0xf0   KEY_BACK>,
 <0xf8   KEY_UP>,
 <0xbb  KEY_DOWN>,
 <0xef   KEY_LEFT>,
 <0xed  KEY_RIGHT>,
 <0xfc   KEY_HOME>,
 <0xf1   KEY_VOLUMEUP>,
 <0xfd   KEY_VOLUMEDOWN>,
 <0xb7  KEY_SEARCH>,
 <0xff    KEY_POWER>,
 <0xf3   KEY_MUTE>,
 <0xbf  KEY_MENU>,
 <0xf9  KEY_ENTER>,
 <0xb3  388>,
 <0xbe  KEY_1>,
 <0xba  KEY_2>,
 <0xb2  KEY_3>,
 <0xbd  KEY_4>,
 <0xf9   KEY_5>,
 <0xb1  KEY_6>,
 <0xfc   KEY_7>,
 <0xf8   KEY_8>,
 <0xb0  KEY_9>,
 <0xb6  KEY_0>,
 <0xb5  KEY_BACKSPACE>;
 };

编译kernel烧录之后,adb shell 输入 getevent 可以看到类似如下打印:

U5X:/ # getevent                                                               
add device 1: /dev/input/event2
  name:     "cec_input"
could not get driver version for /dev/input/mice, Not a typewriter
add device 2: /dev/input/event1
  name:     "gpio_keypad"
could not get driver version for /dev/input/mouse0, Not a typewriter
add device 3: /dev/input/event0
  name:     "aml_keypad"

随便按一个按键可以看到:

/dev/input/event0: 0001 0069 00000001
/dev/input/event0: 0000 0000 00000000
/dev/input/event0: 0001 0069 00000000
/dev/input/event0: 0000 0000 00000000

这串打印表示了一个完整的按键按下和抬起,码值是0x69转换为十进制为105 这个105就是dtsi文件里映射的linux码值。

可以看到遥控的按键事件是/dev/input/event0  对应的输入设备就是

add device 3: /dev/input/event0
  name:     "aml_keypad"

adb shell 下 输入 dumpsys input 可以看到这样的一段打印

3: aml_keypad
      Classes: 0x0000082b
      Path: /dev/input/event0
      Descriptor: d2c52ff0f656fac4cd7b7a118d575e0109a9fe1c
      Location: keypad/input0
      ControllerNumber: 0
      UniqueId: 
      Identifier: bus=0x0010, vendor=0x0001, product=0x0001, version=0x0100
      KeyLayoutFile: /system/usr/keylayout/Vendor_0001_Product_0001.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile: 
      HaveKeyboardLayoutOverlay: false

说明遥控器对应的kl文件是KeyLayoutFile: /system/usr/keylayout/Vendor_0001_Product_0001.kl
我们这个是已经匹配好了kl文件,关于系统匹配kl文件有个优先级的规则:

/system/usr/key1ayout/Vendor_xxx_Product_xxx_Version_xxx.kl
/system/usr/keylayout/Vendor_xxx_Product_xxx.kl
/system/usr/keylayout/DEVICE_NAME.kl
/data/system/devices/keylayout/Vendor_xxx_Product_xxx_Version_xxx.kl
/data/system/devices/keylayout/Vendor_xxx_Product_xxx.kl
/data/system/devices/key1ayout/DEVICE_NAME.kl
/system/usr/key1ayout/Generic.kl
/data/system/devices/keylayout/Generic.kl

具体的代码流程有时间可以去研究一下,系统如果没有匹配到任何kl文件或者匹配你的kl文件出错,就会调用默认的Generic.kl  如果你还没有配置自己的kl文件,dumpsys input 打印可以看这一段:

Identifier: bus=0x0010, vendor=0x0001, product=0x0001,version=0x0100

你可以通过vendor 和 product的值来创建kl文件。
你也可以通过这个设备名aml_keypad来创建kl文件,优先级比VID,PID命名的kl文件低一点。
设备名kl的命名规则是:设备名称中除"0-9"、“a-z”、“A-Z”、"-“或”_“之外的所有字符将替换为下划线,所以这里可以创建一个/system/usr/keylayout/aml_keypad.kl文件

有时候在添加的时候会发现,明明已经创建好了kl文件,但是发现遥控按键还是有问题,看打印信息dumpsys input,匹配的kl文件竟然是Generic.kl这个系统的默认文件,这时候看打印通常会提示kl文件里面某个keycode没有定义。
所以kl文件里面配置的安卓Keycode一定要在这几个文件里有定义好,否则kl匹配会报错就会自动匹配到默认的Generic.kl文件了

frameworks/base/core/java/android/view/KeyEvent.java
frameworks/native/include/android/keycodes.h
frameworks/native/include/input/InputEventLabels.h
frameworks/base/core/res/res/values/attrs.xml

kl文件Vendor_0001_Product_0001.kl创建好后,可以参照系统自带的kl文件来配置linux code和 android keycode的映射。

key 28    ENTER

比如这个key 28 里面的 28就是linux code 对应dtsi文件里面的 KEY_ENTER,为啥我知道KEY_ENTER=28呢?这个当然不是瞎蒙的,是看这个文件 kernel/include/uapi/linux/input.h 知道的:

#define KEY_ENTER 28

当然你可能说我这个文件里面根本没有,那有可能你的是在别的文件里面,大概都是在这个目录下,去找一下,很容易找到的。
那后面的ENTER是怎么回事呢,其实就是frameworks/base/core/java/android/view/KeyEvent.java这个文件里面的,可以打开看一下,很容易就找到了这个定义:

public static final int KEYCODE_ENTER           = 66;

明明是KEYCODE_ENTER 为什么kl里面写成ENTER呢,其实这个我也不知道,我就知道kl里面都是这样配置的,把KEYCODE_ 这一段去掉,保留下划线后面这一段。依葫芦画瓢这样配置好之后就完成了 ‘红外码-->linux code--->android keycode’完整的映射。
这个时候你可以看一下这个frameworks\base\services\core\java\com\android\server\policyPhoneWindowManager.java文件,把里面这个static final boolean DEBUG_INPUT = false;改成true之后,按键按下的时候,就有keycode打印出来,这个就是android keycode了。

可以看到KeyEvent.java里面定义了很多个按键,有几百个,你在键盘上看到的所有按键,基本上都有定义,但是你说我还嫌不够,我想定义几个自己的按键玩玩也是可以的。接下来是时候展示真正的技术了
1.在你的kl文件里面添加

key 252   HEADSET_PTT

2.input.h(kernel/include/uapi/linux/input.h)

#define KEY_HEADSET_PTT 252

3./frameworks/native/include/android/KeyCodes.h

enum {      
    AKEYCODE_HEADSET_PTT         = 276, //276跟其他不能重复 };

4./frameworks/native/include/input/InputEventLabels.h

DEFINE_KEYCODE(HEADSET_PTT),

5.frameworks/base/core/res/res/values/attrs.xml

<attr name="keycode"> ... <enum name="KEYCODE_HEADSET_PTT" value="276" /> ... </attr>

6.frameworks/base/core/java/android/view/KeyEvent.java

/**
* @hide
*/ 
public static final int KEYCODE_HEADSET_PTT     = 276; //这个值和attrs.xml  InputEventLabels.h值相同 
private static final int LAST_KEYCODE = KEYCODE_HEADSET_PTT; 
public static final boolean isSystemKey(int keyCode) {
 ... switch (keyCode) { 
           case KeyEvent.KEYCODE_HEADSET_PTT: //
                     return true;
          }
 ...
  }
... public static final boolean isMediaKey(int keyCode) { //这个也可以加 也可以不加
    switch (keyCode) { 
             case KeyEvent.KEYCODE_HEADSET_PTT: 
                    return true;
          }
}

7.接下来就可以在这个文件里监听你的按键了
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

... @Override 
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
      ... case KeyEvent.KEYCODE_HEADSET_PTT:{
              if(down){
                   ...
                }else{
                   ...
                } break;
            }         
      ...
    }

工作时间匆忙之中整理而成,难免有些词不达意和错漏之处,欢迎批评指正。