Android系统使用global key 一键启动指定APP或者打开WiFi/蓝牙等系统设置界面
理论:
一个Android产品所有的按键分为三大类:
- global key
通用按键,例如AKEYCODE_TV,这些key我们可以修改frameworks\base\core\res\res\xml\Global_keys.xml自己指定。 - system key
系统按键,例如AKEYCODE_HOME(home键) AKEYCODE_VOLUME_DOWN(音量键)。 - user key
用户按键,例如AKEYCODE_A AKEYCODE_B AKEYCODE_1等。
其中system key和user key都是有特殊用途的,所以我们要想实现一键启动的需要必须使用global key,物理硬件上需要有一个额外的没有其他功能的按键来作为启动键。
思路:
硬件: 一个没有其他功能的按键来作为启动键(global key)。
Linux: 写一个按键驱动,捕获按键事件,并上报,假设上报KEY_TV。
Android:获取Linux驱动上报的按键事件,截获AKEYCODE_TV(Linux上报KEY_TV,Android中对应AKEYCODE_TV)按键事件,然后处理(启动一个app,或者打开设置界面等)。
说明:
之前使用过广播的形式,在framework中捕获到对应的按键事件就发送一个广播,然后写一个app,静态注册一个广播接收器,当接收到framework发送的广播说明有用户按下按键,这时在广播接收器中启动对应的app。但是Android5.0之后,Google对发送广播做出了一些处理,当注册广播接收器的app被后台杀死后就无法再接收广播,也就无法再通过这个广播接收器启动其他app了。为了解决这个问题,我摒弃了发送广播的方法,采用修改Android系统源码,在framework层截取输入事件,直接启动其他APP。
关于按键事件:
Linux驱动上报的按键事件称之为EV_KEY,或者scancode 例如KEY_1 KEY_A等。
/system/usr/keylayout/Generic.kl(keylayout文件,Linux层的scancode到Android层的AKEYCODE的对应关系)。
表示按键 对应Linux层(scancode KEY_2) 对应Android层(AKEYCODE_1 AKEYCODE_TV)key 2 1key 377 TV
如果你想自定义global key,则需要按照上面的格式修改Generic.kl添加一项即可。
Android的framework接收的按键事件称之为AKEYCODE(Android KEY CODE), 例如AKEYCODE_1 AKEYCODE_A
/system/usr/keychars/Generic.kcm(key character map文件,用来表示Android按键即AKEYCODE_A对应哪个字符,或者同时按下其他按键(shift/capslock)后,对应哪个字符也就是说,你按下某个按键后,最终在屏幕上显示某个字符是由这个kcm文件所决定的)。
key A { label: 'A' //印在按键上的文字,提示作用 base: 'a' //如果没有其他键(shift capslock)同时按下,则显示字符'b' shift, capslock: 'A' //如果有shift/capslock按键同时按下,则显示字符'B'}
通过以上分析,必须明确Linux kernel驱动上报的按键事件和Android中对应的code的对应关系,比如Linux 驱动上报key_377按键事件,那么查询Generic.kl得知,Android层接收的是AKEYCODE_TV,这一点在修改代码捕获对应的按键事件至关重要。
实现:
1、硬件上选择一个有其他功能的按键来作为启动键。
2、写一个按键驱动key.c,上报一个按键事件(比如KEY_TV,即377,上面分析过),添加到内核中,编译到uImage中。
a. 分配 设置 注册一个input_dev结构体
/* 1. 分配一个input_dev结构体 */input_bt_dev = input_allocate_device();;/* 2. 设置 *//* 2.1 能产生哪类事件 -- 能产生按键事件 */set_bit(EV_KEY, input_bt_dev->evbit);set_bit(EV_REP, input_bt_dev->evbit);/* 2.2 能产生哪些按键事件 -- 能产生377这个按键事件 */set_bit(377, input_bt_dev->keybit); //KEY_TV 377/* 2.3 为android构造一些设备信息 -- Android的framework会解析这些信息 */input_bt_dev->name = "hanpeng";input_bt_dev->id.bustype = 1;input_bt_dev->id.vendor = 0x1234;input_bt_dev->id.product = 0x5678;input_bt_dev->id.version = 1;/* 3. 注册 */input_register_device(input_bt_dev);
b. 实现一个中断处理函数,获取内核中的中断号,然后注册中断。
static irqreturn_t buttons_irq(int irq, void *dev_id){ /* 读取按键接的gpio的val,判断按键是按下了还是松开,关于按键按下了引脚是高电平还是低电平要看具体的原理图 */ int pinval = gpio_get_value(pindesc->pin); /* 高电平表示按键松开了 */ if (pinval) { /* 上报KEY_TV按键松开事件 : 最后一个参数: 0表示上报松开, 1表示上报按下 */ printk("KEY_TV DOWN key_val = %d\n", pindesc->key_val); input_event(input_bt_dev, EV_KEY, pindesc->key_val, 0); input_sync(input_bt_dev); /* 表示一次上报结束 */ } else { /* 上报KEY_TV按键按下事件 */ printk("KEY_TV UP key_val = %d\n", pindesc->key_val); input_event(input_bt_dev, EV_KEY, pindesc->key_val, 1); input_sync(input_bt_dev); } return IRQ_RETVAL(IRQ_HANDLED);}
/* 注册中断 * 中断号的获取: * 老内核(3.x以前),根据芯片手册获取硬件中断号,然后查询内核源码中的irq.h找到对应的内核中的中断号,然后进行注册 * 新内核(3.x以后),硬件信息一般都在设备树中描述了,如果在设备树中描述了这个案件,那么驱动就要先去匹配设备树,然 * 后用platform_get_resource获取中断号了。 */request_irq(irq, buttons_irq, IRQ_TYPE_EDGE_BOTH, "KEY_TV", NULL);
3、分析framework中InputManagerService部分相关代码(主要是InputReaer线程和InputDispatcher线程源码),找到对global key的处理,然后修改代码,截取Linux驱动上报的KEY_TV即在Android中对应的AKEYCODE_TV按键事件。
分析过程:看我另一篇 Android输入系统源码分析
http://blog.csdn.net/hanp_linux/article/details/77915919
修改代码:
分析得知:
InputDispatcher线程对所有的按键事件要先处理,然后再分发给具体的APP,让对应的APP做出相应的处理,但是在分发前,由PhoneWindowManager.java中的interceptKeyBeforeDispatching方法先行处理。
frameworks\base\policy\src\com\android\internal\policy\impl\PhoneWindowManager.java
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) { /* 如果解析后的按键是global key,则调用mGlobalKeyManager.handleGlobalKey这个方法进行处理 */ if (mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { return -1; }}
frameworks\base\policy\src\com\android\internal\policy\impl\GlobalKeyManager.java
boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) { //添加如下代码 /* 判断是否是KEY_TV按键按下了,因为是在Android中,所以判断的是AKEYCODE值 */ if (keyCode == 170) /* frameworks\native\include\android\Keycodes.h AKEYCODE_TV = 170 */ { /* 如果是,则在这个地方干你想干的事情,或者启动一个app,或者打开一个设置界面等 */ //打开蓝牙设置界面的intent,同样,可以改变参数打开WiFi设置等 //Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); //打开另外一个app的intent Intent intent = new Intent(context, com.android.hanp.test.MainActivity.class); /* 如果不加这一句将会报异常:Calling startActivity() from outside of an Activity context * requires the FLAG_ACTIVITY_NEW_TASK flag. * 原因:在Activity的context(上下文环境)之外调用startActivity()方法时需要给Intent设置一个flag创建 * 一个新的任务栈 */ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); /* 返回true表示已经对这个按键事件处理了,InputDispatcher线程则根据返回的true将这个事件给丢弃,不会 再分发给应用程序了。 */ return true; } .....}
操作:
1、将按键驱动 key.c放入Linux的driver/char目录,然后修改Makefile,make uImage,将最终生成的uImage或者zImage从新烧写到板子上。
2、修改frameworks\base\policy\src\com\android\internal\policy\impl\GlobalKeyManager.java
mmm frameworks\base\policy 进行编译
最终生成:out/target/product/tiny4412/system/framework/android.policy.jar
最后将这个android.policy.jar替换板子的/system/framework/android.policy.jar
3、 重启板子,按下对应的按键,就启动了对应的app。
大功告成!
更多相关文章
- Android测试-Monkey Test
- Android复习资料1
- android review--基础知识
- Android:SwipeRefreshLayout和ViewPager滑动冲突的原因和正确的解
- Android实践 -- 监听外置sdcard(TF卡)的插拔事件
- Android(安卓)EventBus使用
- Android触摸事件分发之View篇
- ViewGroup中的onInterceptTouchEvent和onTouchEvent调用时序
- Android处理按钮重复点击事件