Android按键添加和处理的方案
版本号 | 说明 | 作者 | 日期 |
1.0 |
Android按键添加和处理的方案 |
Sky Wang | 2013/06/18 |
需求: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[] = { ...
// 将 wifi 开关按键定义为KEY_F16,
// 处理时,捕获KEY_F16进行处理即可。
{
.desc = "wifi",
.code = KEY_F16,
.adc_value = ,
.gpio = INVALID_GPIO,
.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函数:
static void callback(struct adc_client *client, void *client_param, int result)
{
struct rk29_keys_drvdata *ddata = (struct rk29_keys_drvdata *)client_param;
int i; if(result < EMPTY_ADVALUE)
ddata->result = result; // 依次查找key_button中的按键,判断是否需要响应
for (i = ; i < ddata->nbuttons; i++) {
struct rk29_button_data *bdata = &ddata->data[i];
struct rk29_keys_button *button = bdata->button;
if(!button->adc_value)
continue;
int pre_state = button->adc_state;
if(result < button->adc_value + DRIFT_ADVALUE &&
result > button->adc_value - DRIFT_ADVALUE) { button->adc_state = ;
} else {
button->adc_state = ;
}
// 同步按键状态
synKeyDone(button->code, pre_state, button->adc_state); if(bdata->state != button->adc_state)
mod_timer(&bdata->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
}
return;
}
前面已经说过,这个callback会不断的被adc检测的工作队列调用。若检测到adc值在“某按键定义的adc值范围”内,则该按键被按下;否则,没有按下。
下面是synKeyDone()的代码:
static void synKeyDone(int keycode, int pre_status, int cur_status)
{
if (cur_status == pre_status)
return ; if (keycode==KEY_F16)
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()本身以及它相关的函数如下:
// 保存按键状态的结构体
typedef struct combo_module__t {
unsigned char status_wifikey;
} combo_module_t ; static combo_module_t combo_module; // 设置wifi状态。
// 这是对外提供的接口
void set_wifikey(int on)
{
printk("%s on=%d\n", __func__, on);
combo_module.status_wifikey = on;
}
EXPORT_SYMBOL(set_wifikey); // 应用层读取wifi节点的回调函数
static ssize_t show_wifikey_onoff (struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", combo_module.status_wifikey);
} // 应用层设置wifi节点的回调函数
static ssize_t set_wifikey_onoff (struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
unsigned int val;
if(!(sscanf(buf, "%d\n", &val))) {
printk("%s error\n", __func__);
return -EINVAL;
} if(!val) {
combo_module.status_wifikey = ;
} else {
combo_module.status_wifikey = ;
}
printk("%s status_wifikey=%d\n", __func__, combo_module.status_wifikey); return ;
} // 将wifi的读取/设置函数和节点对应
static ssize_t show_wifikey_onoff (struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t set_wifikey_onoff (struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
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中才能在系统中看到文件节点。下面是实现代码:
// 将wifikey_onoff注册到attribute中
static struct attribute *control_sysfs_entries[] = {
&dev_attr_wifikey_onoff.attr,
NULL
}; static struct attribute_group control_sysfs_attr_group = {
.name = NULL,
.attrs = control_sysfs_entries,
}; // 对应的probe函数。主要作用是将attribute_group注册到sysfs中
static int control_sysfs_probe(struct platform_device *pdev)
{
return sysfs_create_group(&pdev->dev.kobj, &control_sysfs_attr_group);
} // 对应的remove函数。主要作用是将attribute_group从sysfs中注销
static int control_sysfs_remove (struct platform_device *pdev)
{
sysfs_remove_group(&pdev->dev.kobj, &control_sysfs_attr_group); return ;
}
02 将Wifi读取接口注册到Android系统中
通过JNI,将wifi读取接口注册到Android系统中,下面是对应的JNI函数control_service.c的代码:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <fcntl.h>
#include <assert.h> // 获取数组的大小
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[])))
// 指定要注册的类,对应完整的java类名
#define JNIREG_CLASS "com/skywang/control/ControlService" // 引入log头文件
#include <android/log.h> // log标签
#define TAG "WifiControl"
// 定义debug信息
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
// 定义error信息
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__) #define WIFI_ONOFF_CONTROL "/sys/devices/platform/misc_ctl/wifikey_onoff" // 设置wifi电源开关
JNIEXPORT jint JNICALL is_wifi_key_down(JNIEnv *env, jclass clazz)
{
int fd;
int ret;
char buf[]; // LOGD("%s \n", __func__);
if((fd = open(WIFI_ONOFF_CONTROL, O_RDONLY)) < ) {
LOGE("%s : Cannot access \"%s\"", __func__, WIFI_ONOFF_CONTROL);
return; // fd open fail
} memset((void *)buf, 0x00, sizeof(buf));
ssize_t count = read(fd, buf, );
if (count == ) {
buf[count] = '\0';
ret = atoi(buf);
} else {
buf[] = '\0';
} // LOGD("%s buf=%s, ret=%d\n", __func__, buf, ret);
close(fd); return ret;
} // 清除wifi的按下状态
JNIEXPORT void JNICALL clr_wifi_key_status(JNIEnv *env, jclass clazz)
{
int fd;
int nwr;
char buf[]; if((fd = open(WIFI_ONOFF_CONTROL, O_RDWR)) < ) {
LOGE("%s : Cannot access \"%s\"", __func__, WIFI_ONOFF_CONTROL);
return; // fd open fail
} nwr = sprintf(buf, "%d\n", );
write(fd, buf, nwr); LOGE("%s \n", __func__); close(fd);
} // Java和JNI函数的绑定表
static JNINativeMethod method_table[] = {
// wifi按键相关函数
{ "is_wifi_key_down", "()I", (void*)is_wifi_key_down },
{ "clr_wifi_key_status", "()V", (void*)clr_wifi_key_status },
}; // 注册native方法到java中
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < ) {
return JNI_FALSE;
} return JNI_TRUE;
} int register_wifi_control(JNIEnv *env)
{
// 调用注册方法
return registerNativeMethods(env, JNIREG_CLASS,
method_table, NELEM(method_table));
} JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return result;
} register_wifi_control(env); // 返回jni的版本
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的内容如下:
JNINativeMethod method_table[] = {
// wifi按键相关函数
{ "is_wifi_key_down", "()I", (void*)is_wifi_key_down },
{ "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的代码如下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := control_service
LOCAL_SRC_FILES := control_service.c
# 添加对log库的支持
LOCAL_LDLIBS:=-L$(SYSROOT)/usr/lib -llog
# 注:若生成static的.a,只需添加 LOCAL_LDLIBS:=-llog include $(BUILD_SHARED_LIBRARY) 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代码如下:
package com.skywang.control; import android.os.IBinder;
import android.app.Service;
import android.content.Intent;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.util.Log; public class ControlService extends Service {
private static final String TAG = "ControlService"; private WifiManager mWM;
private ReadThread mReadThread;
private boolean bWifi; @Override
public void onCreate() {
super.onCreate(); Log.e(TAG, "start ControlService");
mWM = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
mReadThread = new ReadThread();
mReadThread.start(); bWifi = mWM.isWifiEnabled();
} @Override
public void onDestroy() {
super.onDestroy(); if (mReadThread != null)
mReadThread.interrupt();
} @Override
public IBinder onBind(Intent intent) {
return null;
} // 处理wifi按键
private synchronized void handleWifiKey() {
if (is_wifi_key_down()==1) { // 清空wifi的按下状态。目的是“防止不断的产生wifi按下事件”
clr_wifi_key_status();
Log.d(TAG, "wifi key down");
if (!mWM.isWifiEnabled()) {
Log.e(TAG, "open wifi");
mWM.setWifiEnabled(true);
} else {
Log.e(TAG, "close wifi");
mWM.setWifiEnabled(false);
}
}
} // 和Activity界面通信的接口
private class ReadThread extends Thread { @Override
public void run() {
super.run(); while (!isInterrupted()) {
handleWifiKey();
}
}
} // wifi按键相关函数
private native int is_wifi_key_down();
private native void clr_wifi_key_status(); static {
// 加载本地.so库文件
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的代码:
<service android:name="com.skywang.control.ControlService">
<intent-filter >
<action android:name="com.skywang.control.CONTROLSERVICE" />
</intent-filter>
</service>
我们在Launcher.java的onCreate()函数中启动该服务。这样,随着系统系统服务就会一直运行了。启动服务的代码如下:
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按键相关信息”,代码如下:
static struct rk29_keys_button key_button[] = { ...
// 将 wifi 开关按键定义为KEY_F16,
// 处理时,捕获KEY_F16进行处理即可。
{
.desc = "wifi",
.code = KEY_F16,
.adc_value = ,
.gpio = INVALID_GPIO,
.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函数:
static void callback(struct adc_client *client, void *client_param, int result)
{
struct rk29_keys_drvdata *ddata = (struct rk29_keys_drvdata *)client_param;
int i; if(result < EMPTY_ADVALUE)
ddata->result = result; // 依次查找key_button中的按键,判断是否需要响应
for (i = ; i < ddata->nbuttons; i++) {
struct rk29_button_data *bdata = &ddata->data[i];
struct rk29_keys_button *button = bdata->button;
if(!button->adc_value)
continue;
int pre_state = button->adc_state;
if(result < button->adc_value + DRIFT_ADVALUE &&
result > button->adc_value - DRIFT_ADVALUE) { button->adc_state = ;
} else {
button->adc_state = ;
} if(bdata->state != button->adc_state)
mod_timer(&bdata->timer,
jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL));
}
return;
}
这里的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定义如下:
static const KeycodeLabel KEYCODES[] = {
{ "SOFT_LEFT", },
{ "SOFT_RIGHT", },
{ "HOME", },
{ "BACK", },
{ "CALL", },
{ "ENDCALL", },
{ "", },
{ "", },
{ "", },
{ "", },
{ "", },
{ "", },
{ "", },
{ "", },
{ "", },
{ "", },
{ "STAR", },
{ "POUND", },
{ "DPAD_UP", },
{ "DPAD_DOWN", },
{ "DPAD_LEFT", },
{ "DPAD_RIGHT", },
{ "DPAD_CENTER", },
{ "VOLUME_UP", },
{ "VOLUME_DOWN", },
{ "POWER", },
{ "CAMERA", },
{ "CLEAR", },
{ "A", },
{ "B", },
{ "C", },
{ "D", },
{ "E", },
{ "F", },
{ "G", },
{ "H", },
{ "I", },
{ "J", },
{ "K", },
{ "L", },
{ "M", },
{ "N", },
{ "O", },
{ "P", },
{ "Q", },
{ "R", },
{ "S", },
{ "T", },
{ "U", },
{ "V", },
{ "W", },
{ "X", },
{ "Y", },
{ "Z", },
{ "COMMA", },
{ "PERIOD", },
{ "ALT_LEFT", },
{ "ALT_RIGHT", },
{ "SHIFT_LEFT", },
{ "SHIFT_RIGHT", },
{ "TAB", },
{ "SPACE", },
{ "SYM", },
{ "EXPLORER", },
{ "ENVELOPE", },
{ "ENTER", },
{ "DEL", },
{ "GRAVE", },
{ "MINUS", },
{ "EQUALS", },
{ "LEFT_BRACKET", },
{ "RIGHT_BRACKET", },
{ "BACKSLASH", },
{ "SEMICOLON", },
{ "APOSTROPHE", },
{ "SLASH", },
{ "AT", },
{ "NUM", },
{ "HEADSETHOOK", },
{ "FOCUS", },
{ "PLUS", },
{ "MENU", },
{ "NOTIFICATION", },
{ "SEARCH", },
{ "MEDIA_PLAY_PAUSE", },
{ "MEDIA_STOP", },
{ "MEDIA_NEXT", },
{ "MEDIA_PREVIOUS", },
{ "MEDIA_REWIND", },
{ "MEDIA_FAST_FORWARD", },
{ "MUTE", },
{ "PAGE_UP", },
{ "PAGE_DOWN", },
{ "PICTSYMBOLS", },
{ "SWITCH_CHARSET", },
{ "BUTTON_A", },
{ "BUTTON_B", },
{ "BUTTON_C", },
{ "BUTTON_X", },
{ "BUTTON_Y", },
{ "BUTTON_Z", },
{ "BUTTON_L1", },
{ "BUTTON_R1", },
{ "BUTTON_L2", },
{ "BUTTON_R2", },
{ "BUTTON_THUMBL", },
{ "BUTTON_THUMBR", },
{ "BUTTON_START", },
{ "BUTTON_SELECT", },
{ "BUTTON_MODE", },
{ "ESCAPE", },
{ "FORWARD_DEL", },
{ "CTRL_LEFT", },
{ "CTRL_RIGHT", },
{ "CAPS_LOCK", },
{ "SCROLL_LOCK", },
{ "META_LEFT", },
{ "META_RIGHT", },
{ "FUNCTION", },
{ "SYSRQ", },
{ "BREAK", },
{ "MOVE_HOME", },
{ "MOVE_END", },
{ "INSERT", },
{ "FORWARD", },
{ "MEDIA_PLAY", },
{ "MEDIA_PAUSE", },
{ "MEDIA_CLOSE", },
{ "MEDIA_EJECT", },
{ "MEDIA_RECORD", },
{ "F1", },
{ "F2", },
{ "F3", },
{ "F4", },
{ "F5", },
{ "F6", },
{ "F7", },
{ "F8", },
{ "F9", },
{ "F10", },
{ "F11", },
{ "F12", },
{ "NUM_LOCK", },
{ "NUMPAD_0", },
{ "NUMPAD_1", },
{ "NUMPAD_2", },
{ "NUMPAD_3", },
{ "NUMPAD_4", },
{ "NUMPAD_5", },
{ "NUMPAD_6", },
{ "NUMPAD_7", },
{ "NUMPAD_8", },
{ "NUMPAD_9", },
{ "NUMPAD_DIVIDE", },
{ "NUMPAD_MULTIPLY", },
{ "NUMPAD_SUBTRACT", },
{ "NUMPAD_ADD", },
{ "NUMPAD_DOT", },
{ "NUMPAD_COMMA", },
{ "NUMPAD_ENTER", },
{ "NUMPAD_EQUALS", },
{ "NUMPAD_LEFT_PAREN", },
{ "NUMPAD_RIGHT_PAREN", },
{ "VOLUME_MUTE", },
{ "INFO", },
{ "CHANNEL_UP", },
{ "CHANNEL_DOWN", },
{ "ZOOM_IN", },
{ "ZOOM_OUT", },
{ "TV", },
{ "WINDOW", },
{ "GUIDE", },
{ "DVR", },
{ "BOOKMARK", },
{ "CAPTIONS", },
{ "SETTINGS", },
{ "TV_POWER", },
{ "TV_INPUT", },
{ "STB_POWER", },
{ "STB_INPUT", },
{ "AVR_POWER", },
{ "AVR_INPUT", },
{ "PROG_RED", },
{ "PROG_GREEN", },
{ "PROG_YELLOW", },
{ "PROG_BLUE", },
{ "APP_SWITCH", },
{ "BUTTON_1", },
{ "BUTTON_2", },
{ "BUTTON_3", },
{ "BUTTON_4", },
{ "BUTTON_5", },
{ "BUTTON_6", },
{ "BUTTON_7", },
{ "BUTTON_8", },
{ "BUTTON_9", },
{ "BUTTON_10", },
{ "BUTTON_11", },
{ "BUTTON_12", },
{ "BUTTON_13", },
{ "BUTTON_14", },
{ "BUTTON_15", },
{ "BUTTON_16", },
{ "LANGUAGE_SWITCH", },
{ "MANNER_MODE", },
{ "3D_MODE", },
{ "CONTACTS", },
{ "CALENDAR", },
{ "MUSIC", },
{ "CALCULATOR", },
{ "ZENKAKU_HANKAKU", },
{ "EISU", },
{ "MUHENKAN", },
{ "HENKAN", },
{ "KATAKANA_HIRAGANA", },
{ "YEN", },
{ "RO", },
{ "KANA", },
{ "ASSIST", }, // 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. { NULL, }
};
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按键。
代码如下:
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final int keyCode = event.getKeyCode(); final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0; final boolean keyguardActive = (mKeyguardMediator == null ? false :
(isScreenOn ?
mKeyguardMediator.isShowingAndNotHidden() :
mKeyguardMediator.isShowing())); if (!mSystemBooted) {
return 0;
} if (DEBUG_INPUT) {
Log.d(TAG, "interceptKeyTq keycode=" + keyCode
+ " screenIsOn=" + isScreenOn + " keyguardActive=" + keyguardActive);
} if (down && (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0
&& event.getRepeatCount() == 0) {
performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
} if (keyCode == KeyEvent.KEYCODE_POWER) {
policyFlags |= WindowManagerPolicy.FLAG_WAKE;
}
final boolean isWakeKey = (policyFlags & (WindowManagerPolicy.FLAG_WAKE
| WindowManagerPolicy.FLAG_WAKE_DROPPED)) != 0; int result;
if ((isScreenOn && !mHeadless) || (isInjected && !isWakeKey)) {
// When the screen is on or if the key is injected pass the key to the application.
result = ACTION_PASS_TO_USER;
} else {
// When the screen is off and the key is not injected, determine whether
// to wake the device but don't pass the key to the application.
result = 0;
if (down && isWakeKey) {
if (keyguardActive) {
// If the keyguard is showing, let it decide what to do with the wake key.
mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq(keyCode,
mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED);
} else {
// Otherwise, wake the device ourselves.
result |= ACTION_POKE_USER_ACTIVITY;
}
}
} // Handle special keys.
switch (keyCode) {
case KeyEvent.KEYCODE_SYSRQ: {
if (!down) {
printScreenSysRq();
}
break;
}
case KeyEvent.KEYCODE_AVR_POWER: { Log.d("##SKYWANG##", "global keycode:"+keyCode);
if (keyCode == KeyEvent.KEYCODE_AVR_POWER && down==false) {
// Wifi按键处理
WifiManager mWM = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
boolean bWifi = mWM.isWifiEnabled();
mWM.setWifiEnabled(!bWifi);
}
break;
}
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
if (isScreenOn && !mVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeDownKeyTriggered = true;
mVolumeDownKeyTime = event.getDownTime();
mVolumeDownKeyConsumedByScreenshotChord = false;
cancelPendingPowerKeyAction();
interceptScreenshotChord();
}
} else {
mVolumeDownKeyTriggered = false;
cancelPendingScreenshotChordAction();
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
if (down) {
if (isScreenOn && !mVolumeUpKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeUpKeyTriggered = true;
cancelPendingPowerKeyAction();
cancelPendingScreenshotChordAction();
}
} else {
mVolumeUpKeyTriggered = false;
cancelPendingScreenshotChordAction();
}
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
// add by skywang
if (!down)
handleMuteKey() ;
} if (down) {
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {
try {
if (telephonyService.isRinging()) {
// If an incoming call is ringing, either VOLUME key means
// "silence ringer". We handle these keys here, rather than
// in the InCallScreen, to make sure we'll respond to them
// even if the InCallScreen hasn't come to the foreground yet.
// Look for the DOWN event here, to agree with the "fallback"
// behavior in the InCallScreen.
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " VOLUME key-down while ringing: Silence ringer!"); // Silence the ringer. (It's safe to call this
// even if the ringer has already been silenced.)
telephonyService.silenceRinger(); // And *don't* pass this key thru to the current activity
// (which is probably the InCallScreen.)
result &= ~ACTION_PASS_TO_USER;
break;
}
if (telephonyService.isOffhook()
&& (result & ACTION_PASS_TO_USER) == 0) {
// If we are in call but we decided not to pass the key to
// the application, handle the volume change here.
handleVolumeKey(AudioManager.STREAM_VOICE_CALL, keyCode);
break;
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
} if (isMusicActive() && (result & ACTION_PASS_TO_USER) == 0) {
// If music is playing but we decided not to pass the key to the
// application, handle the volume change here.
handleVolumeKey(AudioManager.STREAM_MUSIC, keyCode);
break;
}
}
break;
} case KeyEvent.KEYCODE_ENDCALL: {
result &= ~ACTION_PASS_TO_USER;
if (down) {
ITelephony telephonyService = getTelephonyService();
boolean hungUp = false;
if (telephonyService != null) {
try {
hungUp = telephonyService.endCall();
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
interceptPowerKeyDown(!isScreenOn || hungUp);
} else {
if (interceptPowerKeyUp(canceled)) {
if ((mEndcallBehavior
& Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0) {
if (goHome()) {
break;
}
}
if ((mEndcallBehavior
& Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) {
result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP;
}
}
}
break;
} case KeyEvent.KEYCODE_POWER: {
if(mHdmiPlugged&&SystemProperties.get("ro.hdmi.power_disable","false").equals("true")){
Log.d("hdmi","power disable---------------");
result=0;
break;
}
result &= ~ACTION_PASS_TO_USER;
if (down) {
if (isScreenOn && !mPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mPowerKeyTriggered = true;
mPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
} ITelephony telephonyService = getTelephonyService();
boolean hungUp = false;
if (telephonyService != null) {
try {
if (telephonyService.isRinging()) {
// Pressing Power while there's a ringing incoming
// call should silence the ringer.
telephonyService.silenceRinger();
} else if ((mIncallPowerBehavior
& Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
&& telephonyService.isOffhook()) {
// Otherwise, if "Power button ends call" is enabled,
// the Power button will hang up any current active call.
hungUp = telephonyService.endCall();
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
interceptPowerKeyDown(!isScreenOn || hungUp
|| mVolumeDownKeyTriggered || mVolumeUpKeyTriggered);
} else {
mPowerKeyTriggered = false;
cancelPendingScreenshotChordAction();
if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) {
result = (result & ~ACTION_POKE_USER_ACTIVITY) | ACTION_GO_TO_SLEEP;
}
mPendingPowerKeyUpCanceled = false;
}
break;
} case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
if (down) {
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {
try {
if (!telephonyService.isIdle()) {
// Suppress PLAY/PAUSE toggle when phone is ringing or in-call
// to avoid music playback.
break;
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
}
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
if ((result & ACTION_PASS_TO_USER) == 0) {
// Only do this if we would otherwise not pass it to the user. In that
// case, the PhoneWindow class will do the same thing, except it will
// only do it if the showing app doesn't process the key on its own.
// Note that we need to make a copy of the key event here because the
// original key event will be recycled when we return.
mBroadcastWakeLock.acquire();
Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK,
new KeyEvent(event));
msg.setAsynchronous(true);
msg.sendToTarget();
}
break;
} case KeyEvent.KEYCODE_CALL: {
if (down) {
ITelephony telephonyService = getTelephonyService();
if (telephonyService != null) {
try {
if (telephonyService.isRinging()) {
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " CALL key-down while ringing: Answer the call!");
telephonyService.answerRingingCall(); // And *don't* pass this key thru to the current activity
// (which is presumably the InCallScreen.)
result &= ~ACTION_PASS_TO_USER;
}
} catch (RemoteException ex) {
Log.w(TAG, "ITelephony threw RemoteException", ex);
}
}
}
break;
}
}
return result;
}
在上面的代码中,我们捕获了KeyEvent.KEYCODE_AVR_POWER,并对其进行处理。
方案结论: 方案可行。而且运行效率比“方案二”高,不会造成功耗很大的问题。
最终总结:方案三最好!