实现方案需求:Android机器上有个Wifi物理按键,现在需求通过点击“wifi物理按键”能够快速的开启/关闭wifi。
经过思考之后,拟出下面几种方案:
方案一,在linux kernel的驱动中捕获“wifi物理按键”。在kernel的按键驱动中截获“wifi”按键,并对其进行处理:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。
方案二,在Android中添加一个服务,监听wifi按键消息。若监听到“wifi”按键,则读取wifi的状态:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。
方案三,在Android的input输入子系统的框架层中捕获wifi按键,并进行相应处理。若捕获到“wifi”按键,则读取wifi的状态:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。
方案一
方案思路: 在linux kernel的驱动中捕获“wifi物理按键”。在kernel的按键驱动中截获“wifi”按键,并对其进行处理:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。
方案分析: 若采用此方案需要解决以下问题
01,在kerne的按键驱动中捕获“wifi”按键。
-- 这个问题很好实现。在kernel的按键驱动中,对按键值进行判断,若是wifi按键,则进行相应处理。
02,在kernel中读取并设置wifi的开/关状态。
-- 这个较难实现。因为wifi驱动的开/关相关的API很难获取到。一般来来说,wifi模组的驱动都是wifi厂家写好并以.ko文件加载的。若需要获取wifi的操作API,需要更厂家一起合作;让它们将接口开放,并让其它设备在kernel中可以读取到。
03,在kernel中将wifi的状态上报到Android系统中。若单单只是实现02步,只是简单的能开/关wifi了;但还需要向办法让Android系统直到wifi的开/关行为。
-- 可以实现,但是太麻烦了。
方案结论: 实现难度太大!
方案二
方案思路: 在Android中添加一个服务,监听wifi按键消息。若监听到“wifi”按键,则读取wifi的状态:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。
方案分析: 若采用此方案需要解决以下问题
01,将kernel的wifi按键上传到Android系统中。
-- 这个可以实现。首先,我们将wifi按键映射到一个sys文件节点上:按下wifi按键时,sys文件节点的值为1;未按下wifi按键时,sys文件节点的值为0。其次,通过NDK编程,读取该sys文件节点,并将读取的接口映射注册到JNI中。最后,通过JNI,将该接口对应注册到Android系统中,使应用程序能够读取该接口。
02,在Android系统中添加一个服务,不断读取wifi按键状态。
-- 这个也可以实现。由于“01”中,我们已经将wifi的按键状态通过JNI注册到Android系统中;我们这里就可以读取到。
03,读取并设置wifi的开/关状态。
-- 这个也可以实现。在Android系统中,我们可以通过WifiManager去读取/设置wifi的开/关状态。通过WifiManager设置的wifi状态,是全局的。
架构图:

具体实现:
通过驱动,将wifi按键状态映射到文件节点。由于不同平台差异,具体的代码接口可能有所差异;我所工作的平台是RK3066,所以还是以此来进行介绍。
01 将kernel的wifi按键上传到Android系统中
在按键驱动中编辑wifi按键的驱动:主要的目的是将wifi按键映射到某个键值上,方便后面Android系统调用。因为Android系统使用的按键值和Linux内核使用的按键值不一样,Android会通过一个键值映射表,将Linux的按键值和Android的按键值映射起来。
我们的项目中,wifi按键是通过ADC值来捕获的,而不是中断。下面是“wifi按键相关信息”,代码如下:
static struct rk29_keys_button key_button[] = {
09 | .active_low = PRESS_LEV_LOW, |
从中,我们可以看出wifi的adc值大概是4,它所对应的按键值(即code值)是KEY_F16。
这里,KEY_F16是我们自己定义的(因为linux中没有wifi开关按键),你也可以定义为别的值。记得两点:一,这里的所定义的wifi的code,必须和Android中要处理的按键值(后面会讲到)保持一致;二,不要使用系统中已用到的值。另外,KEY_F16的值为186,可以参考“include/linux/input.h”文件去查看。
在按键驱动中,会将key_button注册到系统中。在按键驱动中,我们将下面的callback函数注册到adc总线上;adc驱动会通过工作队列,判断的读取adc值,并调用callback,从而判断是否有响应的按键按下。下面是callback函数:
01 | static void callback(struct adc_client *client, void *client_param, int result) |
03 | struct rk29_keys_drvdata *ddata = (struct rk29_keys_drvdata *)client_param; |
06 | if (result < EMPTY_ADVALUE) |
07 | ddata->result = result; |
10 | for (i = 0 ; i < ddata->nbuttons; i++) { |
11 | struct rk29_button_data *bdata = &ddata->data[i]; |
12 | struct rk29_keys_button *button = bdata->button; |
13 | if (!button->adc_value) |
15 | int pre_state = button->adc_state; |
16 | if (result < button->adc_value + DRIFT_ADVALUE && |
17 | result > button->adc_value - DRIFT_ADVALUE) { |
19 | button->adc_state = 1 ; |
21 | button->adc_state = 0 ; |
24 | synKeyDone(button->code, pre_state, button->adc_state); |
26 | if (bdata->state != button->adc_state) |
27 | mod_timer(&bdata->timer, |
28 | jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL)); |
前面已经说过,这个callback会不断的被adc检测的工作队列调用。若检测到adc值在“某按键定义的adc值范围”内,则该按键被按下;否则,没有按下。
下面是synKeyDone()的代码:
1 | static void synKeyDone( int keycode, int pre_status, int cur_status) |
3 | if (cur_status == pre_status) |
7 | set_wifikey(cur_status); |
它的作用是同步wifi按键按下状态,根据wifi按键状态,通过set_wifikey()改变对应wifi节点状态。
例如:wifi键按下时,sys/devices/platform/misc_ctl/wifikey_onoff为1; wifi未按下时,sys/devices/platform/misc_ctl/wifikey_onoff为0。
set_wifikey()本身以及它相关的函数如下:
02 | typedef struct combo_module__t { |
03 | unsigned char status_wifikey; |
06 | static combo_module_t combo_module; |
11 | void set_wifikey( int on) |
13 | printk( "%s on=%dn" , __func__, on); |
14 | combo_module.status_wifikey = on; |
16 | EXPORT_SYMBOL(set_wifikey); |
19 | static ssize_t show_wifikey_onoff (struct device *dev, struct device_attribute *attr, char *buf) |
21 | return sprintf(buf, "%dn" , combo_module.status_wifikey); |
25 | static ssize_t set_wifikey_onoff (struct device *dev, struct device_attribute *attr, |
26 | const char *buf, size_t count) |
29 | if (!(sscanf(buf, "%dn" , &val))) { |
30 | printk( "%s errorn" , __func__); |
35 | combo_module.status_wifikey = 0 ; |
37 | combo_module.status_wifikey = 1 ; |
39 | printk( "%s status_wifikey=%dn" , __func__, combo_module.status_wifikey); |
45 | static ssize_t show_wifikey_onoff (struct device *dev, struct device_attribute *attr, char *buf); |
46 | static ssize_t set_wifikey_onoff (struct device *dev, struct device_attribute *attr, |
47 | const char *buf, size_t count); |
48 | static DEVICE_ATTR(wifikey_onoff, S_IRWXUGO, show_wifikey_onoff, set_wifikey_onoff); |
代码说明:
(01) set_wifikey()提供的对外接口。用于在按键驱动中,当wifi按键按下/松开时调用;这样,就对应的改变wifi节点的值。
(02) DEVICE_ATTR(wifikey_onoff, S_IRWXUGO, show_wifikey_onoff, set_wifikey_onoff); 声明wifi的节点为wifikey_onoff节点,并且设置节点的权限为S_IRWXUGO,设置“应用程序读取节点时的回调函数”为show_wifikey_onoff(),设置“应用程序设置节点时的回调函数”为set_wifikey_onoff(),
DEVICE_ATTR只是声明了wifi节点,具体的注册要先将wifikey_onoff注册到attribute_group中;并且将attribute_group注册到sysfs中才能在系统中看到文件节点。下面是实现代码:
02 | static struct attribute *control_sysfs_entries[] = { |
03 | &dev_attr_wifikey_onoff.attr, |
07 | static struct attribute_group control_sysfs_attr_group = { |
09 | .attrs = control_sysfs_entries, |
14 | static int control_sysfs_probe(struct platform_device *pdev) |
16 | return sysfs_create_group(&pdev->dev.kobj, &control_sysfs_attr_group); |
20 | static int control_sysfs_remove (struct platform_device *pdev) |
22 | sysfs_remove_group(&pdev->dev.kobj, &control_sysfs_attr_group); |
02 将Wifi读取接口注册到Android系统中
通过JNI,将wifi读取接口注册到Android系统中,下面是对应的JNI函数control_service.c的代码:
010 | # define NELEM(x) (( int ) (sizeof(x) / sizeof((x)[ 0 ]))) |
012 | #define JNIREG_CLASS "com/skywang/control/ControlService" |
015 | #include <android/log.h> |
018 | #define TAG "WifiControl" |
020 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) |
022 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) |
024 | #define WIFI_ONOFF_CONTROL "/sys/devices/platform/misc_ctl/wifikey_onoff" |
028 | JNIEXPORT jint JNICALL is_wifi_key_down(JNIEnv *env, jclass clazz) |
035 | if ((fd = open(WIFI_ONOFF_CONTROL, O_RDONLY)) < 0 ) { |
036 | LOGE( "%s : Cannot access " %s "" , __func__, WIFI_ONOFF_CONTROL); |
040 | memset(( void *)buf, 0x00 , sizeof(buf)); |
041 | ssize_t count = read(fd, buf, 1 ); |
056 | JNIEXPORT void JNICALL clr_wifi_key_status(JNIEnv *env, jclass clazz) |
062 | if ((fd = open(WIFI_ONOFF_CONTROL, O_RDWR)) < 0 ) { |
063 | LOGE( "%s : Cannot access " %s "" , __func__, WIFI_ONOFF_CONTROL); |
067 | nwr = sprintf(buf, "%dn" , 0 ); |
070 | LOGE( "%s n" , __func__); |
076 | static JNINativeMethod method_table[] = { |
078 | { "is_wifi_key_down" , "()I" , ( void *)is_wifi_key_down }, |
079 | { "clr_wifi_key_status" , "()V" , ( void *)clr_wifi_key_status }, |
083 | static int registerNativeMethods(JNIEnv* env, const char * className, |
084 | JNINativeMethod* gMethods, int numMethods) |
087 | clazz = (*env)->FindClass(env, className); |
091 | if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0 ) { |
098 | int register_wifi_control(JNIEnv *env) |
101 | return registerNativeMethods(env, JNIREG_CLASS, |
102 | method_table, NELEM(method_table)); |
105 | JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void * reserved) |
110 | if ((*vm)->GetEnv(vm, ( void **) &env, JNI_VERSION_1_4) != JNI_OK) { |
114 | register_wifi_control(env); |
117 | return JNI_VERSION_1_4; |
代码说明:
(01) Android 的JVM会回调JNI_OnLoad()函数。在JNI_OnLoad()中,调用register_wifi_control(env)。
(02) register_wifi_control(env)调用 registerNativeMethods(env, JNIREG_CLASS, method_table,
NELEM(method_table)) 将method_table表格中的函数注册到Android的JNIREG_CLASS类中。
JNIREG_CLASS为com/skywang/control/ControlService,所以它对应的类是com.skywang.control.ControlService.java。
(03) method_table的内容如下:
1 | JNINativeMethod method_table[] = { |
3 | { "is_wifi_key_down" , "()I" , ( void *)is_wifi_key_down }, |
4 | { "clr_wifi_key_status" , "()V" , ( void *)clr_wifi_key_status }, |
这意味着,将该文件中的is_wifi_key_down()函数和JNIREG_CLASS类的is_wifi_key_down()绑定。
将该文件中的clr_wifi_key_status()函数和JNIREG_CLASS类的clr_wifi_key_status()绑定。
该文件对应的Android.mk的代码如下:
01 | LOCAL_PATH := $(call my-dir) |
05 | LOCAL_MODULE := control_service |
06 | LOCAL_SRC_FILES := control_service.c |
08 | LOCAL_LDLIBS:=-L$(SYSROOT)/usr/lib -llog |
09 | # 注:若生成 static 的.a,只需添加 LOCAL_LDLIBS:=-llog |
11 | include $(BUILD_SHARED_LIBRARY) |
13 | LOCAL_PATH := $(call my-dir) |
用ndk-build编译上面两个文件,得到so库文件libcontrol_service.so。
关于Android NDK编程更详细的内容,请参考“Android JNI和NDK学习”
03 Android读取wifi的开关/状态
在Android创建一个com.skywang.control.ControlService.java。例如,在Launcher的目录下创建packages/apps/Launcher2/src/com/skywang/control/ControlService.java
ControlService.java代码如下:
01 | package com.skywang.control; |
03 | import android.os.IBinder; |
04 | import android.app.Service; |
05 | import android.content.Intent; |
06 | import android.content.Context; |
07 | import android.net.wifi.WifiManager; |
08 | import android.util.Log; |
10 | public class ControlService extends Service { |
11 | private static final String TAG = "ControlService" ; |
13 | private WifiManager mWM; |
14 | private ReadThread mReadThread; |
15 | private boolean bWifi; |
18 | public void onCreate() { |
21 | Log.e(TAG, "start ControlService" ); |
22 | mWM = (WifiManager) this .getSystemService(Context.WIFI_SERVICE); |
23 | mReadThread = new ReadThread(); |
26 | bWifi = mWM.isWifiEnabled(); |
30 | public void onDestroy() { |
33 | if (mReadThread != null ) |
34 | mReadThread.interrupt(); |
38 | public IBinder onBind(Intent intent) { |
43 | private synchronized void handleWifiKey() { |
44 | if (is_wifi_key_down()== 1 ) { |
47 | clr_wifi_key_status(); |
48 | Log.d(TAG, "wifi key down" ); |
49 | if (!mWM.isWifiEnabled()) { |
50 | Log.e(TAG, "open wifi" ); |
51 | mWM.setWifiEnabled( true ); |
53 | Log.e(TAG, "close wifi" ); |
54 | mWM.setWifiEnabled( false ); |
60 | private class ReadThread extends Thread { |
66 | while (!isInterrupted()) { |
73 | private native int is_wifi_key_down(); |
74 | private native void clr_wifi_key_status(); |
78 | System.loadLibrary( "control_service" ); |
代码说明:
(01) System.loadLibrary("control_service"); 这是在ControlService启动的时候自动执行的,目的是加载libcontrol_service.so库。即上一步所生成的so库文件。
(02) ControlService.java是服务程序,它继承于Service。ReadThread是启动时会自动开启的线程。ReadThread的作用就是不断的调用handleWifiKey()处理wifi按键值。
接下来,我们在AndroidManifest.xml中声明该服务,就可以在其它地方调用执行了。下面是manifest中声明ControlService的代码:
1 | < service android:name = "com.skywang.control.ControlService" > |
3 | < action android:name = "com.skywang.control.CONTROLSERVICE" /> |
我们在Launcher.java的onCreate()函数中启动该服务。这样,随着系统系统服务就会一直运行了。启动服务的代码如下:
1 | startService( new Intent( "com.skywang.control.CONTROLSERVICE" )); |
方案结论: 工作正常,但消耗系统资源较多,会增加系统功耗!
经过测试发现,此方案运行很正常。但存在一个问题:由于添加了一个不停运行的服务,消耗很多系统资源,导致机器的功能也增加了很多。
因此,再实现方案三,对比看看效果如何。
方案三
方案思路: 在Android的input输入子系统的框架层中捕获wifi按键,并进行相应处理。若捕获到“wifi”按键,则读取wifi的状态:若是“wifi”是开启的,则关闭wifi;否则,打开wifi。
方案分析: 若采用此方案需要解决以下问题
01, 将kernel的wifi按键值映射到Android系统的某键值上。
-- 这个可以实现。和“方案二”一样,我们通过ADC驱动将wifi按键映射到键值KEY_F16上;然后,将kernel的KEY_F16和Android的某一键值对应。
02, 在Android的framework层的键值处理函数中,捕获按键,并进行相应处理。
-- 这个可以实现。在input子系统的framework层,捕获到wifi按键对应的Android系统中的按键
架构图:

具体实现:
01, 将kernel的wifi按键值映射到Android系统的某键值上。
01.01, wifi按键驱动
在按键驱动中编辑wifi按键的驱动:主要的目的是将wifi按键映射到某个键值上,方便后面Android系统调用。因为Android系统使用的按键值和Linux内核使用的按键值不一样,Android会通过一个键值映射表,将Linux的按键值和Android的按键值映射起来。
我们的项目中,wifi按键是通过ADC值来捕获的,而不是中断。下面是“wifi按键相关信息”,代码如下:
01 | static struct rk29_keys_button key_button[] = { |
11 | .active_low = PRESS_LEV_LOW, |
从中,我们可以看出wifi的adc值大概是4,它所对应的按键值(即code值)是KEY_F16。
这里,KEY_F16是我们自己定义的(因为linux中没有wifi开关按键),你也可以定义为别的值。记得两点:一,这里的所定义的wifi的code,必须和Android中要处理的按键值(后面会讲到)保持一致;二,不要使用系统中已用到的值。另外,KEY_F16的值为186,可以参考“include/linux/input.h”文件去查看。
在按键驱动中,会将key_button注册到系统中。在按键驱动中,我们将下面的callback函数注册到adc总线上;adc驱动会通过工作队列,判断的读取adc值,并调用callback,从而判断是否有响应的按键按下。下面是callback函数:
01 | static void callback(struct adc_client *client, void *client_param, int result) |
03 | struct rk29_keys_drvdata *ddata = (struct rk29_keys_drvdata *)client_param; |
06 | if (result < EMPTY_ADVALUE) |
07 | ddata->result = result; |
10 | for (i = 0 ; i < ddata->nbuttons; i++) { |
11 | struct rk29_button_data *bdata = &ddata->data[i]; |
12 | struct rk29_keys_button *button = bdata->button; |
13 | if (!button->adc_value) |
15 | int pre_state = button->adc_state; |
16 | if (result < button->adc_value + DRIFT_ADVALUE && |
17 | result > button->adc_value - DRIFT_ADVALUE) { |
19 | button->adc_state = 1 ; |
21 | button->adc_state = 0 ; |
24 | if (bdata->state != button->adc_state) |
25 | mod_timer(&bdata->timer, |
26 | jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL)); |
这里的callback和“方案二”中的callback有区别。这里没有调用synKeyDone()函数。
01.02, 键值映射
映射文件:
Linux中的按键值和Android中的按键值不一样。它们是通过.kl映射文件,建立对应关系的。
默认的映射文件是 qwerty.kl;但不同的平台可能有效的映射文件不同。用户可以通过查看"/system/usr/keylayout/"目录下的.kl映射文件,来进行验证哪个是有效的。映射方法:一,可以通过查看调用.kl的代码。二,修改.kl文件来验证。
在rk3066中,有效的映射文件是“rk29-keypad.kl”。在“rk29-keypad.kl”中添加以下代码将wifi按键和Android中的“AVR_POWER按键”对应。
key 186 AVR_POWER
说明:
key -- 是关键字。固定值,不需要改变。
186 -- wifi按键在linux驱动中对应的键值,这里对应KEY_F16的键值,即wifi对应的按键值。关于linux驱动中的各个键值,可以查看“include/linux/input.h”
AVR_POWER -- wifi按键映射到Android中的按键,它对应是“KeycodeLabels.h”文件中的KEYCODES表格元素的“literal”值。
KeycodeLabels.h中KEYCODES定义如下:
001 | static const KeycodeLabel KEYCODES[] = { |
023 | { "DPAD_RIGHT" , 22 }, |
024 | { "DPAD_CENTER" , 23 }, |
026 | { "VOLUME_DOWN" , 25 }, |
060 | { "SHIFT_LEFT" , 59 }, |
061 | { "SHIFT_RIGHT" , 60 }, |
072 | { "LEFT_BRACKET" , 71 }, |
073 | { "RIGHT_BRACKET" , 72 }, |
076 | { "APOSTROPHE" , 75 }, |
080 | { "HEADSETHOOK" , 79 }, |
084 | { "NOTIFICATION" , 83 }, |
086 | { "MEDIA_PLAY_PAUSE" , 85 }, |
087 | { "MEDIA_STOP" , 86 }, |
088 | { "MEDIA_NEXT" , 87 }, |
089 | { "MEDIA_PREVIOUS" , 88 }, |
090 | { "MEDIA_REWIND" , 89 }, |
091 | { "MEDIA_FAST_FORWARD" , 90 }, |
095 | { "PICTSYMBOLS" , 94 }, |
096 | { "SWITCH_CHARSET" , 95 }, |
103 | { "BUTTON_L1" , 102 }, |
104 | { "BUTTON_R1" , 103 }, |
105 | { "BUTTON_L2" , 104 }, |
106 | { "BUTTON_R2" , 105 }, |
107 | { "BUTTON_THUMBL" , 106 }, |
108 | { "BUTTON_THUMBR" , 107 }, |
109 | { "BUTTON_START" , 108 }, |
110 | { "BUTTON_SELECT" , 109 }, |
111 | { "BUTTON_MODE" , 110 }, |
113 | { "FORWARD_DEL" , 112 }, |
114 | { "CTRL_LEFT" , 113 }, |
115 | { "CTRL_RIGHT" , 114 }, |
116 | { "CAPS_LOCK" , 115 }, |
117 | { "SCROLL_LOCK" , 116 }, |
118 | { "META_LEFT" , 117 }, |
119 | { "META_RIGHT" , 118 }, |
123 | { "MOVE_HOME" , 122 }, |
127 | { "MEDIA_PLAY" , 126 }, |
128 | { "MEDIA_PAUSE" , 127 }, |
129 | { "MEDIA_CLOSE" , 128 }, |
130 | { "MEDIA_EJECT" , 129 }, |
131 | { "MEDIA_RECORD" , 130 }, |
155 | { "NUMPAD_DIVIDE" , 154 }, |
156 | { "NUMPAD_MULTIPLY" , 155 }, |
157 | { "NUMPAD_SUBTRACT" , 156 }, |
158 | { "NUMPAD_ADD" , 157 }, |
159 | { "NUMPAD_DOT" , 158 }, |
160 | { "NUMPAD_COMMA" , 159 }, |
161 | { "NUMPAD_ENTER" , 160 }, |
162 | { "NUMPAD_EQUALS" , 161 }, |
163 | { "NUMPAD_LEFT_PAREN" , 162 }, |
164 | { "NUMPAD_RIGHT_PAREN" , 163 }, |
165 | { "VOLUME_MUTE" , 164 }, |
167 | { "CHANNEL_UP" , 166 }, |
168 | { "CHANNEL_DOWN" , 167 }, |
180 | { "STB_POWER" , 179 }, |
181 | { "STB_INPUT" , 180 }, |
182 | { "AVR_POWER" , 181 }, |
183 | { "AVR_INPUT" , 182 }, |
185 | { "PROG_GREEN" , 184 }, |
186 | { "PROG_YELLOW" , 185 }, |
187 | { "PROG_BLUE" , 186 }, |
188 | { "APP_SWITCH" , 187 }, |
198 | { "BUTTON_10" , 197 }, |
199 | { "BUTTON_11" , 198 }, |
200 | { "BUTTON_12" , 199 }, |
201 | { "BUTTON_13" , 200 }, |
202 | { "BUTTON_14" , 201 }, |
203 | { "BUTTON_15" , 202 }, |
204 | { "BUTTON_16" , 203 }, |
205 | { "LANGUAGE_SWITCH" , 204 }, |
206 | { "MANNER_MODE" , 205 }, |
211 | { "CALCULATOR" , 210 }, |
212 | { "ZENKAKU_HANKAKU" , 211 }, |
216 | { "KATAKANA_HIRAGANA" , 215 }, |
KeycodeLabels.h中的按键与Android框架层的KeyEvent.java中的按键值对应。
例如:“AVR_POWER”对应“KeyEvent.java中的键值”如下:
public static final int KEYCODE_AVR_POWER = 181;
这样,我们发现:将驱动的wifi按键就和Android系统中的KEYCODE_AVR_POWER就对应起来了!
02, 在Android的framework层的键值处理函数中,捕获按键,并进行相应处理。
在framework层的input系统中,加入对wifi按键的捕获。
添加的文件是:frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
添加的具体方法:在PhoneWindowManager.java的interceptKeyBeforeQueueing()函数中,捕获wifi按键。
代码如下:
001 | public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { |
002 | final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; |
003 | final boolean canceled = event.isCanceled(); |
004 | final int keyCode = event.getKeyCode(); |
006 | final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0 ; |
008 | final boolean keyguardActive = (mKeyguardMediator == null ? false : |
010 | mKeyguardMediator.isShowingAndNotHidden() : |
011 | mKeyguardMediator.isShowing())); |
013 | if (!mSystemBooted) { |
018 | Log.d(TAG, "interceptKeyTq keycode=" + keyCode |
019 | + " screenIsOn=" + isScreenOn + " keyguardActive=" + keyguardActive); |
023 | if (down && (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0 |
024 | && event.getRepeatCount() == 0 ) { |
025 | performHapticFeedbackLw( null , HapticFeedbackConstants.VIRTUAL_KEY, false ); |
028 | if (keyCode == KeyEvent.KEYCODE_POWER) { |
029 | policyFlags |= WindowManagerPolicy.FLAG_WAKE; |
031 | final boolean isWakeKey = (policyFlags & (WindowManagerPolicy.FLAG_WAKE |
032 | | WindowManagerPolicy.FLAG_WAKE_DROPPED)) != 0 ; |
035 | if ((isScreenOn && !mHeadless) || (isInjected && !isWakeKey)) { |
037 | result = ACTION_PASS_TO_USER; |
042 | if (down && isWakeKey) { |
043 | if (keyguardActive) { |
045 | mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq(keyCode, |
046 | mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED); |
049 | result |= ACTION_POKE_USER_ACTIVITY; |
056 | case KeyEvent.KEYCODE_SYSRQ: { |
062 | case KeyEvent.KEYCODE_AVR_POWER: { |
064 | Log.d( "##SKYWANG##" , "global keycode:" +keyCode); |
065 | if (keyCode == KeyEvent.KEYCODE_AVR_POWER && down== false ) { |
067 | WifiManager mWM = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
068 | boolean bWifi = mWM.isWifiEnabled(); |
069 | mWM.setWifiEnabled(!bWifi); |
073 | case KeyEvent.KEYCODE_VOLUME_DOWN: |
074 | case KeyEvent.KEYCODE_VOLUME_UP: |
075 | case KeyEvent.KEYCODE_VOLUME_MUTE: { |
076 | if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { |
078 | if (isScreenOn && !mVolumeDownKeyTriggered |
079 | && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0 ) { |
080 | mVolumeDownKeyTriggered = true ; |
081 | mVolumeDownKeyTime = event.getDownTime(); |
082 | mVolumeDownKeyConsumedByScreenshotChord = false ; |
083 | cancelPendingPowerKeyAction(); |
084 | interceptScreenshotChord(); |
087 | mVolumeDownKeyTriggered = false ; |
088 | cancelPendingScreenshotChordAction(); |
090 | } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { |
092 | if (isScreenOn && !mVolumeUpKeyTriggered |
093 | && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0 ) { |
094 | mVolumeUpKeyTriggered = true ; |
095 | cancelPendingPowerKeyAction(); |
096 | cancelPendingScreenshotChordAction(); |
099 | mVolumeUpKeyTriggered = false ; |
100 | cancelPendingScreenshotChordAction(); |
102 | } else if (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) { |
109 | ITelephony telephonyService = getTelephonyService(); |
110 | if (telephonyService != null ) { |
112 | if (telephonyService.isRinging()) { |
119 | Log.i(TAG, "interceptKeyBeforeQueueing:" |
120 | + " VOLUME key-down while ringing: Silence ringer!" ); |
124 | telephonyService.silenceRinger(); |
128 | result &= ~ACTION_PASS_TO_USER; |
131 | if (telephonyService.isOffhook() |
132 | && (result & ACTION_PASS_TO_USER) == 0 ) { |
135 | handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode); |
138 | } catch (RemoteException ex) { |
139 | Log.w(TAG, "ITelephony threw RemoteException" , ex); |
143 | if (isMusicActive() && (result & ACTION_PASS_TO_USER) == 0 ) { |
146 | handleVolumeKey(AudioManager.STREAM_MUSIC, keyCode); |
153 | case KeyEvent.KEYCODE_ENDCALL: { |
154 | result &= ~ACTION_PASS_TO_USER; |
156 | ITelephony telephonyService = getTelephonyService(); |
157 | boolean hungUp = false ; |
158 | if (telephonyService != null ) { |
160 | hungUp = telephonyService.endCall(); |
161 | } catch (RemoteException ex) { |
162 | Log.w(TAG, "ITelephony threw RemoteException" , ex); |
165 | interceptPowerKeyDown(!isScreenOn || hungUp); |
167 | if (interceptPowerKeyUp(canceled)) { |
168 | if ((mEndcallBehavior |
169 | & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0 ) { |
174 | if ((mEndcallBehavior |
175 | & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0 ) { |
176 | result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP; |
183 | case KeyEvent.KEYCODE_POWER: { |
184 | if (mHdmiPlugged&&SystemProperties.get( "ro.hdmi.power_disable" , "false" ).equals( "true" )){ |
185 | Log.d( "hdmi" , "power disable---------------" ); |
189 | result &= ~ACTION_PASS_TO_USER; |
191 | if (isScreenOn && !mPowerKeyTriggered |
192 | && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0 ) { |
193 | mPowerKeyTriggered = true ; |
194 | mPowerKeyTime = event.getDownTime(); |
195 | interceptScreenshotChord(); |
198 | ITelephony telephonyService = getTelephonyService(); |
199 | boolean hungUp = false ; |
200 | if (telephonyService != null ) { |
202 | if (telephonyService.isRinging()) { |
205 | telephonyService.silenceRinger(); |
206 | } else if ((mIncallPowerBehavior |
207 | & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 |
208 | && telephonyService.isOffhook()) { |
211 | hungUp = telephonyService.endCall(); |
213 | } catch (RemoteException ex) { |
214 | Log.w(TAG, "ITelephony threw RemoteException" , ex); |
217 | interceptPowerKeyDown(!isScreenOn || hungUp |
218 | || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered); |
220 | mPowerKeyTriggered = false ; |
221 | cancelPendingScreenshotChordAction(); |
222 | if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) { |
223 | result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP; |
225 | mPendingPowerKeyUpCanceled = false ; |
230 | case KeyEvent.KEYCODE_MEDIA_PLAY: |
231 | case KeyEvent.KEYCODE_MEDIA_PAUSE: |
232 | case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: |
234 | ITelephony telephonyService = getTelephonyService(); |
235 | if (telephonyService != null ) { |
237 | if (!telephonyService.isIdle()) { |
242 | } catch (RemoteException ex) { |
243 | Log.w(TAG, "ITelephony threw RemoteException" , ex); |
247 | case KeyEvent.KEYCODE_HEADSETHOOK: |
248 | case KeyEvent.KEYCODE_MUTE: |
249 | case KeyEvent.KEYCODE_MEDIA_STOP: |
250 | case KeyEvent.KEYCODE_MEDIA_NEXT: |
251 | case KeyEvent.KEYCODE_MEDIA_PREVIOUS: |
252 | case KeyEvent.KEYCODE_MEDIA_REWIND: |
253 | case KeyEvent.KEYCODE_MEDIA_RECORD: |
254 | case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { |
255 | if ((result & ACTION_PASS_TO_USER) == 0 ) { |
261 | mBroadcastWakeLock.acquire(); |
262 | Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK, |
263 | new KeyEvent(event)); |
264 | msg.setAsynchronous( true ); |
270 | case KeyEvent.KEYCODE_CALL: { |
272 | ITelephony telephonyService = getTelephonyService(); |
273 | if (telephonyService != null ) { |
275 | if (telephonyService.isRinging()) { |
276 | Log.i(TAG, "interceptKeyBeforeQueueing:" |
277 | + " CALL key-down while ringing: Answer the call!" ); |
278 | telephonyService.answerRingingCall(); |
282 | result &= ~ACTION_PASS_TO_USER; |
284 | } catch (RemoteException ex) { |
285 | Log.w(TAG, "ITelephony threw RemoteException" , ex); |
在上面的代码中,我们捕获了KeyEvent.KEYCODE_AVR_POWER,并对其进行处理。
方案结论: 方案可行。而且运行效率比“方案二”高,不会造成功耗很大的问题。
最终总结:方案三最好!