Android 底层新增按键系统上层适配详解

Android 底层新增按键系统上层适配详解

文章目录

一、前言

在Android系统 上外界按键板或者在原有的按键新增个按键都是需要内核和系统层进行代码适配。

二、网上的一个参考

网上参考的新增键值的示例:

网址:https://www.cnblogs.com/blogs-of-lxl/p/9490205.html

复制代码
(1)Kernel层:

 ① include/uapi/linux/input.h 中添加: 
		#define KEY_LXL               123
 ② drivers/hid/hid-input.c 中添加:                
		case 0x188: map_key_clear(KEY_LXL);      break;   //其中0x188是HID设备上报的原始键值


(2)Android系统层:

 ① bionic/libc/kernel/uapi/linux/input-event-codes.h 中添加  : 
		#define KEY_LXL               123    //与kernel中头文件定义一致
 ② Generic.kl或Vendor_xxxx_Product_xxxx.kl文件中添加      :  
		key 123 LXL;
 ③ /frameworks/native/include/android/keycodes.h  中添加  :  
		AKEYCODE_LXL          = 666,
 ④ /frameworks/native/include/input/KeycodeLabels.h 的 KEYCODES[]数组中添加: 
		{ "LXL", 666 },
 ⑤ 在frameworks/base/core/res/res/values/attrs.xml 中添加 :  
		<enum name="KEYCODE_LXL" value="666" />
 ⑥ 在frameworks/base/core/java/android/view/KeyEvent.java添加: 
		public static final int KEYCODE_LXL= 666;

从上面看主要映射流程如下:

复制代码
内核底层  KEY_LXL  123
系统底层1  KEY_LXL  123
kl 文件  key 123 LXL //其实这里应该是错误的,查看BACK按键就知道了,这里是用的上层的键值。
系统底层2  AKEYCODE_LXL  666
系统上层  KEYCODE_LXL      666

上面的适配代码,从Android11上已经看不到 第 ④ 点了!

上面的内容估计是Android 比较旧的代码,和最新的看起来有点差别,下面是基于Android13 上的一次上层按键适配。

三、按键基本准备和信息查看

实际项目是安卓设备接入了按键板,添加了一些自定义按键功能,

比如新增设置菜单,待机等功能,并且保留音量设置、返回、Home等基本功能。

举例:按键内核上添加了按键 MYKEY,按键板的节点内核名称是 ttmk

复制代码
    field public static final int KEYCODE_MYKEY = 601; // 0x259

getevent 大概会有如下信息:

复制代码
console:/ # getevent
add device 1: /dev/input/event9
  name:     "KTC IRTOUCH T600"
add device 2: /dev/input/event1
  name:     "ttmk"
。。。


/dev/input/event1: 0001 0259 00000001 //按下 MYKEY
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0001 0259 00000000 //抬起 MYKEY
/dev/input/event1: 0000 0000 00000000

窗口显示的字符串数值是 16进制的,转换成10进制就是底层定义的按键数值。0x259 = 601

所以这里可以看出底层定义的按键数值是601.

getevent -l 大概会有如下信息:

复制代码
console:/ # getevent -l
add device 1: /dev/input/event9
  name:     "KTC IRTOUCH T600"
add device 2: /dev/input/event1
  name:     "ttmk"
。。。
/dev/input/event1: EV_KEY       KEY_BACK             DOWN                
/dev/input/event1: EV_SYN       SYN_REPORT           00000000            
/dev/input/event1: EV_KEY       KEY_BACK             UP                  
/dev/input/event1: EV_SYN       SYN_REPORT           00000000      

/dev/input/event1: EV_KEY       KEY_MYKEY             DOWN                
/dev/input/event1: EV_SYN       SYN_REPORT           00000000            
/dev/input/event1: EV_KEY       KEY_MYKEY             UP                  
/dev/input/event1: EV_SYN       SYN_REPORT           00000000      

dumpsys input 大概会有如下信息:

复制代码
atom:/ # dumpsys input
INPUT MANAGER (dumpsys input)

Input Manager State:
  Interactive: false
  System UI Visibility: 0x8008
  Pointer Speed: 0
  Pointer Gestures Enabled: true
  Show Touches: false
  Pointer Capture Enabled: false

Event Hub State: //(1)事件状态信息是主要关注的
  BuiltInKeyboardId: -2
  Devices: //(2)Devices里面的每个信息都是对应不同的节点信息
    -1: Virtual
      Classes: 0x40000023
      Path: <virtual> (3)关注Path字符串,就是节点的位置,这里是虚拟,不清楚具体意义
      Enabled: true
      Descriptor: a718a782d34bc767f4689c232d64d527998ea7fd
      Location:
      ControllerNumber: 0
      UniqueId: <virtual>
      Identifier: bus=0x0000, vendor=0x0000, product=0x0000, version=0x0000
      KeyLayoutFile: /system/usr/keylayout/Generic.kl
      KeyCharacterMapFile: /system/usr/keychars/Virtual.kcm
      ConfigurationFile:
      HaveKeyboardLayoutOverlay: false
      VideoDevice: <none>
    1: ttmk
      Classes: 0x00000200
      Path: /dev/input/event2
      Enabled: true
      Descriptor: 65195a4ab35c59e79bbba55177be90fc42ed3ae6
      Location:
      ControllerNumber: 0
      UniqueId:
      Identifier: bus=0x0000, vendor=0x0000, product=0x0000, version=0x0000
      KeyLayoutFile: /system/usr/keylayout/Generic.kl
      KeyCharacterMapFile: /system/usr/keychars/Virtual.kcm
      ConfigurationFile:
      HaveKeyboardLayoutOverlay: false
      VideoDevice: <none>

上面ok后,就要适配framework 上的代码了,让framework和应用能够接收底层键值。

四、Android13上 framework 上的实际修改

内核上没有项目实际代码,不进行展示,只介绍framework 上的修改。

1、native/cpp 相关文件修改

(1)头文件定义 按键键值字符串和键值数值

frameworks\native\include\android\keycodes.h

复制代码
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, //原生数值
。。。
	//add MY key ,自定义提交的数值
    AKEYCODE_MY_SHUTDOWN_READY = 600,
    AKEYCODE_MYKEY = 601,
    AKEYCODE_MY_OPS = 602,
    AKEYCODE_MY_WHITEBOARD = 603,
    AKEYCODE_MY_SHOTSCREEN = 604,
    AKEYCODE_MY_SOURCE = 605,
    AKEYCODE_MY_SHUTDOWN_OPS_ONLY = 606,
    AKEYCODE_MY_SHUTDOWN_DIRECT = 607
    // NOTE: If you add a new keycode here you must also add it to several other files.
    //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
};

这里定义的是 AKEYCODE_XXX,字符串其实怎么定义都ok,只要cpp文件中能读取到就ok。

但是键值要定义和内核是一致的。

(2)按键处理代码 InputEventLabels.cpp

frameworks\native\libs\input\InputEventLabels.cpp

复制代码
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key } //这里定义默认拼接了 AKEYCODE_
#define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis }

namespace android {

// NOTE: If you add a new keycode here you must also add it to several other files.
//       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
#define KEYCODES_SEQUENCE \
    DEFINE_KEYCODE(UNKNOWN), \
    DEFINE_KEYCODE(SOFT_LEFT), \
    DEFINE_KEYCODE(SOFT_RIGHT), \
    DEFINE_KEYCODE(HOME), \
    DEFINE_KEYCODE(BACK), \
    DEFINE_KEYCODE(CALL), \
    DEFINE_KEYCODE(ENDCALL), \
...
	//自定义的按键
    DEFINE_KEYCODE(MY_SHUTDOWN_READY), \
    DEFINE_KEYCODE(MYKEY), \
    DEFINE_KEYCODE(MY_OPS), \
    DEFINE_KEYCODE(MY_WHITEBOARD), \
    DEFINE_KEYCODE(MY_SHOTSCREEN), \
    DEFINE_KEYCODE(MY_SOURCE), \
    DEFINE_KEYCODE(MY_SHUTDOWN_OPS_ONLY), \
    DEFINE_KEYCODE(MY_SHUTDOWN_DIRECT)

}

这里就是绑定了keycodes.h 里面的 AKEYCODE_XX 按键的键值。

所以按键的键值是关键。并且这个键值跟底层是一致的。

具体是如何绑定的过程这里不分析哈哈。

2、Java 相关文件修改

(1) res attrs资源文件定义键值字符串和键值

frameworks\base\core\res\res\values\attrs.xml

定义键值的资源id,方便代码中获取。

复制代码
    <attr name="keycode">
        <enum name="KEYCODE_UNKNOWN" value="0" /> 
        <enum name="KEYCODE_SOFT_LEFT" value="1" />
        <enum name="KEYCODE_SOFT_RIGHT" value="2" />
        <enum name="KEYCODE_HOME" value="3" />
        <enum name="KEYCODE_BACK" value="4" />
        <enum name="KEYCODE_CALL" value="5" />
        <enum name="KEYCODE_ENDCALL" value="6" /> //原生代码
		...
        <enum name="KEYCODE_MY_SHUTDOWN_READY" value="600" /> //自定义添加的按键代码
        <enum name="KEYCODE_MYKEY" value="601" />
        <enum name="KEYCODE_MY_OPS" value="602" />
        <enum name="KEYCODE_MY_WHITEBOARD" value="603" />
        <enum name="KEYCODE_MY_SHOTSCREEN" value="604" />
        <enum name="KEYCODE_MY_SOURCE" value="605" />
        <enum name="KEYCODE_MY_SHUTDOWN_OPS_ONLY" value="606" />

其实这个资源配置应该没啥用了,按键很少地方用res的属性了,用法:R.attr.keycode.KEYCODE_XX.

但是原生代码里面有,万一是遍历所有键值里面用到呢,所以保险起见还是加一下代码。

有试过去除了这个了类的修改,好像没啥问题!

(2) KeyEvent 定义键值字符串和键值

这个定义就是代码中用来判断某个逻辑处理使用到的。

frameworks\base\core\java\android\view\KeyEvent.java

复制代码
public class KeyEvent extends InputEvent implements Parcelable {

    public static final int KEYCODE_HOME            = 3;
    /** Key code constant: Back key. */
    public static final int KEYCODE_BACK            = 4; //原生按键定义

    //add MY new key 自定义的按键键值
    public static final int KEYCODE_MY_SHUTDOWN_READY = 600;
    public static final int KEYCODE_MY_MENU = 601;
    public static final int KEYCODE_MY_OPS = 602;
    public static final int KEYCODE_MY_WHITEBOARD = 603;
    public static final int KEYCODE_MY_SHOTSCREEN = 604;
    public static final int KEYCODE_MY_SOURCE = 605;
    public static final int KEYCODE_MY_SHUTDOWN_OPS_ONLY = 606;
    public static final int KEYCODE_MY_SHUTDOWN_DIRECT = 607;

}

这里可以看到键值定义的也是内核一样的键值。

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

3、配置文件修改

(1)系统api文件:frameworks/base/core/api/current.txt
复制代码
package android {
  public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable {
    field public static final int KEYCODE_BACK = 4; // 0x4 原生的返回键值
...
    field public static final int KEYCODE_SHIFT_RIGHT = 60; // 0x3c 原生的键盘右边shift键值
    field public static final int KEYCODE_MYKEY = 601; // 0x259, 新增的键值 MYKEY, input keyevent 也是输入601 即可。
	}
}

这个文件的修改在有的环境可能不需要,也可能在当前目录下的 system-current.txt 适配,编译报错的情况才需要修改这个api文件。

我看了下系统适配方案上Android9上是不用适配的,Android11、Android13 上都是要适配这个文件的。

(2)添加或修改kl文件

一般是device下加入XXX.kl,如果device 没有合适的目录放在 framework对应的kl目录下也是可以的

复制代码
key 158      BACK
key 139      MENU
key 102      HOME //保留部分使用到的按键值

#my add //自定义添加的一些按键字符串和按键值
key 600 MY_SHUTDOWN_READY
key 601 MYKEY //某个
key 602	MY_OPS
key 603	MY_WHITEBOARD
key 604	MY_SHOTSCREEN
key 605	MY_SOURCE
key 606	MY_SHUTDOWN_OPS_ONLY

这里定义的键值就不一定需要和内核一致,但是如果不冲突的情况下,定义成和内核一致是没啥问题的。

这里可以看到返回按键底层定义的键值的4 ,上层定义的键值是158.那么这个158 有啥用??

系统默认kl保存目录:

复制代码
framework/base/data/keyboards/Generic.kl

该目录下保存了非常多的kl文件,都是会复制到 system/usr/keylayout/ 目录下的

其实 framework/base/data 目录下面还有其他的类型文件也是会复制到 system/date下面的

大概目录:

复制代码
MYAndroid13/release/frameworks/base/data$ ls -l
drwxrwxr-x 3 rdy087 rdy087  4096  8月 23 14:24 etc //存放 permission 那些私有权限等文件
drwxrwxr-x 2 rdy087 rdy087  4096  5月  8 16:04 fonts // 系统字体文件
drwxrwxr-x 2 rdy087 rdy087 12288  5月  8 16:04 keyboards //系统按键相关kl、kcm、idc文件
drwxrwxr-x 8 rdy087 rdy087  4096  5月  8 11:21 sounds // 系统铃声等声音文件
drwxrwxr-x 2 rdy087 rdy087  4096  5月  8 11:21 videos // 系统小视频文件
MYAndroid13/release/frameworks/base/data$ 
(3)复制kl文件

随便一个可以 PRODUCT_COPY_FILES 的mk文件加入代码代码就可以:

复制代码
PRODUCT_COPY_FILES += \
    device/rockchip/common/rk29-keypad.kl:system/usr/keylayout/rk29-keypad.kl \
。。。
    device/XXX/XXX.kl:system/usr/keylayout/ttmk.kl //复制自己的kl文件到固定目录

系统编译后,会在运行的安卓系统的对应目录下保存 ttmk.kl 文件。

五、其他

1、适配后无效问题

如果按键按下后,getevent没有反应就要内核或者硬件帮忙分析了;

如果按键按下后, getevent 日志信息,但是Java代码中未收到事件。

一般是kl文件未添加到对应键值导致,解决方法:

复制代码
1、getevent 查看按键节点信息
2、dumpsys input 查看当前键值使用的kl文件
3、cat 查看文件里面是否定义有新增的键值
4、直接修改kl文件内容,重启后,测试是否ok

2、键值日志 KeyEvent 对象打印 日志问题:

PhoneWindowManager 可以打印按键 KeyEvent对象。

logcat 查看KeyEvent对象的日志:

复制代码
//按下返回按键的日志,正常获取键值, BACK 是底层定义的键值符合, 4 是底层定义的键值数值,158 是上层定义的键值数值
KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=158, eventTime=440918814000,deviceId=8... }
getKeyCode = 4

//按下自定义按键的日志,未获取到键值,其实是未获取到底层对应的键值,上层的是 601 
KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_UNKNOWN, scanCode=601,  eventTime=275979277000, deviceId=8...}
KeyEvent getKeyCode = 0 

这里可以看到自定义的按键,上层未能识别。

这是因为 kl 文件,未添加对于的键值关键字和键值数值,或底层无法识别对应键值导致的。

2、按键处理的地方

一般情况是在 PhoneWindowManager.interceptKeyBeforeDispatching 进行判断处理

复制代码
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
 public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
            int policyFlags) {
			//判断event 对象,进行对应处理
	}

按键的处理在 PhoneWindowManager.java 中进行处理,前提是能够获取到keycode 值。

3、获取按键节点名称

复制代码
//按键节点名称获取方法:
KeyEvent event;
String deviceName = InputDevice.getDevice(event.getDeviceId()).getName();

这个有些情况下是有用的,主要是按键类别判断吧。

这个名称和 dumpsys input 里面的名称是一致的。

复制代码
atom:/ # dumpsys input

  Devices: //Devices里面的每个信息都是对应不同的节点信息
  -1: vatt  //节点名称 deviceName ,内核定义
      Classes: 0x40000023
      Path: /dev/input/event1 //具体节点位置
      Identifier: bus=0x0000, vendor=0x0000, product=0x0000, version=0x0000
      KeyLayoutFile: /system/usr/keylayout/Generic.kl //实际起作用的kl文件
      KeyCharacterMapFile: /system/usr/keychars/Virtual.kcm
  1: aw8624_haptic  //节点名称
      Classes: 0x00000200
      Path: /dev/input/event2
。。。

4、keycode 为数值的情况

这种情况是功能正常的,但是为啥keycode不像默认键值那样显示字符串?

复制代码
//按下返回按键的日志,正常获取键值, BACK 是底层定义的键值符合, 4 是底层定义的键值数值,158 是上层定义的键值数值
KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=158, eventTime=440918814000,deviceId=8... }
getKeyCode = 4

//按下自定义按键的日志,上层的是 601 ,底层键值是601 是没啥问题的,但是为啥keyCode 为啥不显示字符串
KeyEvent { action=ACTION_DOWN, keyCode=601, scanCode=601,  eventTime=275979277000, deviceId=8...}
KeyEvent getKeyCode = 601 

可以继续往下了解!

5、查看 KeyEvent.keyCodeToString 方法源码

参考:

https://www.jianshu.com/p/a7ebc88842cb

查看KeyEvent 源码:

frameworks\base\core\java\android\view\KeyEvent.java

复制代码
public class KeyEvent extends InputEvent implements Parcelable {

    @Override
    public String toString() { //(1)找到toString方法,这是整个对象的打印信息
        StringBuilder msg = new StringBuilder();
        msg.append("KeyEvent { action=").append(actionToString(mAction));
        msg.append(", keyCode=").append(keyCodeToString(mKeyCode)); //(2)查看keyCode 的打印,其实是打印字符串
        msg.append(", scanCode=").append(mScanCode); //(2)查看scanCode ,这个是上层代码定义的数值
        if (mCharacters != null) {
            msg.append(", characters=\"").append(mCharacters).append("\"");
        }
        msg.append(", metaState=").append(metaStateToString(mMetaState));
        msg.append(", flags=0x").append(Integer.toHexString(mFlags));
        msg.append(", repeatCount=").append(mRepeatCount);
        msg.append(", eventTime=").append(mEventTime);
        msg.append(", downTime=").append(mDownTime);
        msg.append(", deviceId=").append(mDeviceId);
        msg.append(", source=0x").append(Integer.toHexString(mSource));
        msg.append(", displayId=").append(mDisplayId);
        msg.append(" }");
        return msg.toString();
    }

	//这个返回的是按键值的字符串,但是从实现上看底层未返回就直接打印数值
    public static String keyCodeToString(int keyCode) {
        String symbolicName = nativeKeyCodeToString(keyCode);
        return symbolicName != null ? LABEL_PREFIX + symbolicName : Integer.toString(keyCode);
    }

	//这个才是打印按键底层数值的方法
    public final int getKeyCode() {
        return mKeyCode;
    }

上面的新增按键值打印的 keyCode 为数值,就是因为底层方法 nativeKeyCodeToString 未返回字符串导致。

具体流程这里不进行追踪了。

具体原因是因为内核对应的代码文件未声明按键符号。

也就是下面的 input-event-codes.h 文件的修改。

从上面参考的示例里面就有说到要修改 input-event-codes.h 文件,

但是从我这个系统代码里面未看到那个目录的文件所以未进行修改。

但是全局搜索 input-event-codes.h 文件还是可以搜索到的,可以完成修改。

6、input-event-codes.h 的修改

bionic\libc\kernel\uapi\linux\input-event-codes.h

复制代码
#define KEY_BACK		158	/* AC Back */
#define KEY_FORWARD		159	/* AC Forward */
#define KEY_CLOSECD		160
#define KEY_EJECTCD		161

...

//add MY key ,下面的字符串对内核上的字符串
#define KEY_MY_SHUTDOWN_READY = 600,
#define KEY_MY_MENU = 601,
#define KEY_MY_OPS = 602,
#define KEY_MY_WHITEBOARD = 603,
#define KEY_TEST_SHOTSCREEN = 604,
#define KEY_MY_SOURCE = 605,
#define KEY_MY_SHUTDOWN_OPS_ONLY = 606,
#define KEY_MY_SHUTDOWN_DIRECT = 607

但是其实改了这里没用!

具体原因其实追一下 nativeKeyCodeToString 方法的实现就知道了。

键值必须按顺序添加否则数组就超标了,这里实际只有300多个键值,定义的下标已经超了,是找不到的!

数值大不影响,能用就行。

六、添加按键修改总结

①系统上层代码修改内容

1、native/cpp 相关文件修改
复制代码
(1)头文件定义 按键键值字符串和键值数值
frameworks\native\include\android\keycodes.h

(2)按键处理代码 InputEventLabels.cpp
frameworks\native\libs\input\InputEventLabels.cpp
2、Java 相关文件修改
复制代码
(1) res attrs资源文件定义键值字符串和键值
frameworks\base\core\res\res\values\attrs.xml


(2) KeyEvent 定义键值字符串和键值
这个定义就是代码中用来判断某个逻辑处理使用到的。
frameworks\base\core\java\android\view\KeyEvent.java

(3)按键具体处理
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
3、配置文件修改
复制代码
(1)系统api文件:frameworks/base/core/api/current.txt 

(2)添加或修改kl文件:一般是device下加入XXX.kl,如果device 没有合适的目录放在 framework对应的kl目录下也是可以的

(3)复制kl文件:随便一个可以 PRODUCT_COPY_FILES 的mk文件加入代码代码就可以:

②上层代码适配后无效?

1、查看基本信息 getevent , dumpsys input

查看键值是否正常收到;

查看kl文件里面的键值是否正确;

3、在 PhoneWindowManager 中 打印 KeyEvent 的信息,查看是否正常。
相关推荐
一起搞IT吧2 小时前
相机Camera日志实例分析之五:相机Camx【萌拍闪光灯后置拍照】单帧流程日志详解
android·图像处理·数码相机
浩浩乎@2 小时前
【openGLES】安卓端EGL的使用
android
Kotlin上海用户组4 小时前
Koin vs. Hilt——最流行的 Android DI 框架全方位对比
android·架构·kotlin
zzq19964 小时前
Android framework 开发者模式下,如何修改动画过度模式
android
木叶丸4 小时前
Flutter 生命周期完全指南
android·flutter·ios
阿幸软件杂货间5 小时前
阿幸课堂随机点名
android·开发语言·javascript
没有了遇见5 小时前
Android 渐变色整理之功能实现<二>文字,背景,边框,进度条等
android
没有了遇见6 小时前
Android RecycleView 条目进入和滑出屏幕的渐变阴影效果
android
站在巨人肩膀上的码农6 小时前
去掉长按遥控器power键后提示关机、飞行模式的弹窗
android·安卓·rk·关机弹窗·power键·长按·飞行模式弹窗
呼啦啦--隔壁老王7 小时前
屏幕旋转流程
android