Android按鍵添加和處理的方案
實現(xiàn)方案需求:Android機器上有個Wifi物理按鍵,現(xiàn)在需求通過點擊“wifi物理按鍵”能夠快速的開啟/關(guān)閉wifi。
經(jīng)過思考之后,擬出下面幾種方案:
方案一,在linux kernel的驅(qū)動中捕獲“wifi物理按鍵”。在kernel的按鍵驅(qū)動中截獲“wifi”按鍵,并對其進行處理:若是“wifi”是開啟的,則關(guān)閉wifi;否則,打開wifi。
方案二,在Android中添加一個服務(wù),監(jiān)聽wifi按鍵消息。若監(jiān)聽到“wifi”按鍵,則讀取wifi的狀態(tài):若是“wifi”是開啟的,則關(guān)閉wifi;否則,打開wifi。
方案三,在Android的input輸入子系統(tǒng)的框架層中捕獲wifi按鍵,并進行相應(yīng)處理。若捕獲到“wifi”按鍵,則讀取wifi的狀態(tài):若是“wifi”是開啟的,則關(guān)閉wifi;否則,打開wifi。
方案一
方案思路: 在linux kernel的驅(qū)動中捕獲“wifi物理按鍵”。在kernel的按鍵驅(qū)動中截獲“wifi”按鍵,并對其進行處理:若是“wifi”是開啟的,則關(guān)閉wifi;否則,打開wifi。
方案分析: 若采用此方案需要解決以下問題
01,在kerne的按鍵驅(qū)動中捕獲“wifi”按鍵。
-- 這個問題很好實現(xiàn)。在kernel的按鍵驅(qū)動中,對按鍵值進行判斷,若是wifi按鍵,則進行相應(yīng)處理。
02,在kernel中讀取并設(shè)置wifi的開/關(guān)狀態(tài)。
-- 這個較難實現(xiàn)。因為wifi驅(qū)動的開/關(guān)相關(guān)的API很難獲取到。一般來來說,wifi模組的驅(qū)動都是wifi廠家寫好并以.ko文件加載的。若需要獲取wifi的操作API,需要更廠家一起合作;讓它們將接口開放,并讓其它設(shè)備在kernel中可以讀取到。
03,在kernel中將wifi的狀態(tài)上報到Android系統(tǒng)中。若單單只是實現(xiàn)02步,只是簡單的能開/關(guān)wifi了;但還需要向辦法讓Android系統(tǒng)直到wifi的開/關(guān)行為。
-- 可以實現(xiàn),但是太麻煩了。
方案結(jié)論: 實現(xiàn)難度太大!
方案二
方案思路: 在Android中添加一個服務(wù),監(jiān)聽wifi按鍵消息。若監(jiān)聽到“wifi”按鍵,則讀取wifi的狀態(tài):若是“wifi”是開啟的,則關(guān)閉wifi;否則,打開wifi。
方案分析: 若采用此方案需要解決以下問題
01,將kernel的wifi按鍵上傳到Android系統(tǒng)中。
-- 這個可以實現(xiàn)。首先,我們將wifi按鍵映射到一個sys文件節(jié)點上:按下wifi按鍵時,sys文件節(jié)點的值為1;未按下wifi按鍵時,sys文件節(jié)點的值為0。其次,通過NDK編程,讀取該sys文件節(jié)點,并將讀取的接口映射注冊到JNI中。最后,通過JNI,將該接口對應(yīng)注冊到Android系統(tǒng)中,使應(yīng)用程序能夠讀取該接口。
02,在Android系統(tǒng)中添加一個服務(wù),不斷讀取wifi按鍵狀態(tài)。
-- 這個也可以實現(xiàn)。由于“01”中,我們已經(jīng)將wifi的按鍵狀態(tài)通過JNI注冊到Android系統(tǒng)中;我們這里就可以讀取到。
03,讀取并設(shè)置wifi的開/關(guān)狀態(tài)。
-- 這個也可以實現(xiàn)。在Android系統(tǒng)中,我們可以通過WifiManager去讀取/設(shè)置wifi的開/關(guān)狀態(tài)。通過WifiManager設(shè)置的wifi狀態(tài),是全局的。
架構(gòu)圖:
具體實現(xiàn):
通過驅(qū)動,將wifi按鍵狀態(tài)映射到文件節(jié)點。由于不同平臺差異,具體的代碼接口可能有所差異;我所工作的平臺是RK3066,所以還是以此來進行介紹。
01 將kernel的wifi按鍵上傳到Android系統(tǒng)中
在按鍵驅(qū)動中編輯wifi按鍵的驅(qū)動:主要的目的是將wifi按鍵映射到某個鍵值上,方便后面Android系統(tǒng)調(diào)用。因為Android系統(tǒng)使用的按鍵值和Linux內(nèi)核使用的按鍵值不一樣,Android會通過一個鍵值映射表,將Linux的按鍵值和Android的按鍵值映射起來。
我們的項目中,wifi按鍵是通過ADC值來捕獲的,而不是中斷。下面是“wifi按鍵相關(guān)信息”,代碼如下:
static struct rk29_keys_button key_button[] = {
... // 將 wifi 開關(guān)按鍵定義為KEY_F16, // 處理時,捕獲KEY_F16進行處理即可。 { .desc = "wifi", .code = KEY_F16, .adc_value = 4, .gpio = INVALID_GPIO, .active_low = PRESS_LEV_LOW, }, ... };
從中,我們可以看出wifi的adc值大概是4,它所對應(yīng)的按鍵值(即code值)是KEY_F16。
這里,KEY_F16是我們自己定義的(因為linux中沒有wifi開關(guān)按鍵),你也可以定義為別的值。記得兩點:一,這里的所定義的wifi的code,必須和Android中要處理的按鍵值(后面會講到)保持一致;二,不要使用系統(tǒng)中已用到的值。另外,KEY_F16的值為186,可以參考“include/linux/input.h”文件去查看。
在按鍵驅(qū)動中,會將key_button注冊到系統(tǒng)中。在按鍵驅(qū)動中,我們將下面的callback函數(shù)注冊到adc總線上;adc驅(qū)動會通過工作隊列,判斷的讀取adc值,并調(diào)用callback,從而判斷是否有響應(yīng)的按鍵按下。下面是callback函數(shù):
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中的按鍵,判斷是否需要響應(yīng) for (i = 0; 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 = 1; } else { button->adc_state = 0; } // 同步按鍵狀態(tài) 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; }
前面已經(jīng)說過,這個callback會不斷的被adc檢測的工作隊列調(diào)用。若檢測到adc值在“某按鍵定義的adc值范圍”內(nèi),則該按鍵被按下;否則,沒有按下。
下面是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按鍵按下狀態(tài),根據(jù)wifi按鍵狀態(tài),通過set_wifikey()改變對應(yīng)wifi節(jié)點狀態(tài)。
例如:wifi鍵按下時,sys/devices/platform/misc_ctl/wifikey_onoff為1; wifi未按下時,sys/devices/platform/misc_ctl/wifikey_onoff為0。
set_wifikey()本身以及它相關(guān)的函數(shù)如下:
// 保存按鍵狀態(tài)的結(jié)構(gòu)體 typedef struct combo_module__t { unsigned char status_wifikey; } combo_module_t ; static combo_module_t combo_module; // 設(shè)置wifi狀態(tài)。 // 這是對外提供的接口 void set_wifikey(int on) { printk("%s on=%d\n", __func__, on); combo_module.status_wifikey = on; } EXPORT_SYMBOL(set_wifikey); // 應(yīng)用層讀取wifi節(jié)點的回調(diào)函數(shù) static ssize_t show_wifikey_onoff (struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", combo_module.status_wifikey); } // 應(yīng)用層設(shè)置wifi節(jié)點的回調(diào)函數(shù) 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 = 0; } else { combo_module.status_wifikey = 1; } printk("%s status_wifikey=%d\n", __func__, combo_module.status_wifikey); return 0; } // 將wifi的讀取/設(shè)置函數(shù)和節(jié)點對應(yīng) 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()提供的對外接口。用于在按鍵驅(qū)動中,當(dāng)wifi按鍵按下/松開時調(diào)用;這樣,就對應(yīng)的改變wifi節(jié)點的值。
(02) DEVICE_ATTR(wifikey_onoff, S_IRWXUGO, show_wifikey_onoff, set_wifikey_onoff); 聲明wifi的節(jié)點為wifikey_onoff節(jié)點,并且設(shè)置節(jié)點的權(quán)限為S_IRWXUGO,設(shè)置“應(yīng)用程序讀取節(jié)點時的回調(diào)函數(shù)”為show_wifikey_onoff(),設(shè)置“應(yīng)用程序設(shè)置節(jié)點時的回調(diào)函數(shù)”為set_wifikey_onoff(),
DEVICE_ATTR只是聲明了wifi節(jié)點,具體的注冊要先將wifikey_onoff注冊到attribute_group中;并且將attribute_group注冊到sysfs中才能在系統(tǒng)中看到文件節(jié)點。下面是實現(xiàn)代碼:
// 將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, }; // 對應(yīng)的probe函數(shù)。主要作用是將attribute_group注冊到sysfs中 static int control_sysfs_probe(struct platform_device *pdev) { return sysfs_create_group(&pdev->dev.kobj, &control_sysfs_attr_group); } // 對應(yīng)的remove函數(shù)。主要作用是將attribute_group從sysfs中注銷 static int control_sysfs_remove (struct platform_device *pdev) { sysfs_remove_group(&pdev->dev.kobj, &control_sysfs_attr_group); return 0; }
02 將Wifi讀取接口注冊到Android系統(tǒng)中
通過JNI,將wifi讀取接口注冊到Android系統(tǒng)中,下面是對應(yīng)的JNI函數(shù)control_service.c的代碼:
#include <stdlib.h> #include <string.h> #include <stdio.h> #include <jni.h> #include <fcntl.h> #include <assert.h> // 獲取數(shù)組的大小 # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) // 指定要注冊的類,對應(yīng)完整的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" // 設(shè)置wifi電源開關(guān) JNIEXPORT jint JNICALL is_wifi_key_down(JNIEnv *env, jclass clazz) { int fd; int ret; char buf[2]; // LOGD("%s \n", __func__); if((fd = open(WIFI_ONOFF_CONTROL, O_RDONLY)) < 0) { 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, 1); if (count == 1) { buf[count] = '\0'; ret = atoi(buf); } else { buf[0] = '\0'; } // LOGD("%s buf=%s, ret=%d\n", __func__, buf, ret); close(fd); return ret; } // 清除wifi的按下狀態(tài) JNIEXPORT void JNICALL clr_wifi_key_status(JNIEnv *env, jclass clazz) { int fd; int nwr; char buf[2]; if((fd = open(WIFI_ONOFF_CONTROL, O_RDWR)) < 0) { LOGE("%s : Cannot access \"%s\"", __func__, WIFI_ONOFF_CONTROL); return; // fd open fail } nwr = sprintf(buf, "%d\n", 0); write(fd, buf, nwr); LOGE("%s \n", __func__); close(fd); } // Java和JNI函數(shù)的綁定表 static JNINativeMethod method_table[] = { // wifi按鍵相關(guān)函數(shù) { "is_wifi_key_down", "()I", (void*)is_wifi_key_down }, { "clr_wifi_key_status", "()V", (void*)clr_wifi_key_status }, }; // 注冊native方法到j(luò)ava中 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) < 0) { return JNI_FALSE; } return JNI_TRUE; } int register_wifi_control(JNIEnv *env) { // 調(diào)用注冊方法 return registerNativeMethods(env, JNIREG_CLASS, method_table, NELEM(method_table)); } JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; 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會回調(diào)JNI_OnLoad()函數(shù)。在JNI_OnLoad()中,調(diào)用register_wifi_control(env)。
(02) register_wifi_control(env)調(diào)用 registerNativeMethods(env, JNIREG_CLASS, method_table, NELEM(method_table)) 將method_table表格中的函數(shù)注冊到Android的JNIREG_CLASS類中。JNIREG_CLASS為com/skywang/control/ControlService,所以它對應(yīng)的類是com.skywang.control.ControlService.java。
(03) method_table的內(nèi)容如下:
JNINativeMethod method_table[] = { // wifi按鍵相關(guān)函數(shù) { "is_wifi_key_down", "()I", (void*)is_wifi_key_down }, { "clr_wifi_key_status", "()V", (void*)clr_wifi_key_status }, }
這意味著,將該文件中的is_wifi_key_down()函數(shù)和JNIREG_CLASS類的is_wifi_key_down()綁定。
將該文件中的clr_wifi_key_status()函數(shù)和JNIREG_CLASS類的clr_wifi_key_status()綁定。
該文件對應(yīng)的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。
關(guān)于Android NDK編程更詳細的內(nèi)容,請參考“Android JNI和NDK學(xué)習(xí)”
03 Android讀取wifi的開關(guān)/狀態(tài)
在Android創(chuàng)建一個com.skywang.control.ControlService.java。例如,在Launcher的目錄下創(chuàng)建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的按下狀態(tài)。目的是“防止不斷的產(chǎn)生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按鍵相關(guān)函數(shù) 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啟動的時候自動執(zhí)行的,目的是加載libcontrol_service.so庫。即上一步所生成的so庫文件。
(02) ControlService.java是服務(wù)程序,它繼承于Service。ReadThread是啟動時會自動開啟的線程。ReadThread的作用就是不斷的調(diào)用handleWifiKey()處理wifi按鍵值。
接下來,我們在AndroidManifest.xml中聲明該服務(wù),就可以在其它地方調(diào)用執(zhí)行了。下面是manifest中聲明ControlService的代碼:
<service android:name="com.skywang.control.ControlService"> <intent-filter > <action android:name="com.skywang.control.CONTROLSERVICE" /> </intent-filter> </service>
我們在Launcher.java的onCreate()函數(shù)中啟動該服務(wù)。這樣,隨著系統(tǒng)系統(tǒng)服務(wù)就會一直運行了。啟動服務(wù)的代碼如下:
startService(new Intent("com.skywang.control.CONTROLSERVICE"));
方案結(jié)論: 工作正常,但消耗系統(tǒng)資源較多,會增加系統(tǒng)功耗!
經(jīng)過測試發(fā)現(xiàn),此方案運行很正常。但存在一個問題:由于添加了一個不停運行的服務(wù),消耗很多系統(tǒng)資源,導(dǎo)致機器的功能也增加了很多。
因此,再實現(xiàn)方案三,對比看看效果如何。
方案三
方案思路: 在Android的input輸入子系統(tǒng)的框架層中捕獲wifi按鍵,并進行相應(yīng)處理。若捕獲到“wifi”按鍵,則讀取wifi的狀態(tài):若是“wifi”是開啟的,則關(guān)閉wifi;否則,打開wifi。
方案分析: 若采用此方案需要解決以下問題
01, 將kernel的wifi按鍵值映射到Android系統(tǒng)的某鍵值上。
-- 這個可以實現(xiàn)。和“方案二”一樣,我們通過ADC驅(qū)動將wifi按鍵映射到鍵值KEY_F16上;然后,將kernel的KEY_F16和Android的某一鍵值對應(yīng)。
02, 在Android的framework層的鍵值處理函數(shù)中,捕獲按鍵,并進行相應(yīng)處理。
-- 這個可以實現(xiàn)。在input子系統(tǒng)的framework層,捕獲到wifi按鍵對應(yīng)的Android系統(tǒng)中的按鍵
架構(gòu)圖:
具體實現(xiàn):
01, 將kernel的wifi按鍵值映射到Android系統(tǒng)的某鍵值上。
01.01, wifi按鍵驅(qū)動
在按鍵驅(qū)動中編輯wifi按鍵的驅(qū)動:主要的目的是將wifi按鍵映射到某個鍵值上,方便后面Android系統(tǒng)調(diào)用。因為Android系統(tǒng)使用的按鍵值和Linux內(nèi)核使用的按鍵值不一樣,Android會通過一個鍵值映射表,將Linux的按鍵值和Android的按鍵值映射起來。
我們的項目中,wifi按鍵是通過ADC值來捕獲的,而不是中斷。下面是“wifi按鍵相關(guān)信息”,代碼如下:
static struct rk29_keys_button key_button[] = { ... // 將 wifi 開關(guān)按鍵定義為KEY_F16, // 處理時,捕獲KEY_F16進行處理即可。 { .desc = "wifi", .code = KEY_F16, .adc_value = 4, .gpio = INVALID_GPIO, .active_low = PRESS_LEV_LOW, }, ... };
從中,我們可以看出wifi的adc值大概是4,它所對應(yīng)的按鍵值(即code值)是KEY_F16。
這里,KEY_F16是我們自己定義的(因為linux中沒有wifi開關(guān)按鍵),你也可以定義為別的值。記得兩點:一,這里的所定義的wifi的code,必須和Android中要處理的按鍵值(后面會講到)保持一致;二,不要使用系統(tǒng)中已用到的值。另外,KEY_F16的值為186,可以參考“include/linux/input.h”文件去查看。
在按鍵驅(qū)動中,會將key_button注冊到系統(tǒng)中。在按鍵驅(qū)動中,我們將下面的callback函數(shù)注冊到adc總線上;adc驅(qū)動會通過工作隊列,判斷的讀取adc值,并調(diào)用callback,從而判斷是否有響應(yīng)的按鍵按下。下面是callback函數(shù):
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中的按鍵,判斷是否需要響應(yīng) for (i = 0; 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 = 1; } else { button->adc_state = 0; } if(bdata->state != button->adc_state) mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(DEFAULT_DEBOUNCE_INTERVAL)); } return; }
這里的callback和“方案二”中的callback有區(qū)別。這里沒有調(diào)用synKeyDone()函數(shù)。
01.02, 鍵值映射
映射文件:
Linux中的按鍵值和Android中的按鍵值不一樣。它們是通過.kl映射文件,建立對應(yīng)關(guān)系的。
默認的映射文件是 qwerty.kl;但不同的平臺可能有效的映射文件不同。用戶可以通過查看"/system/usr/keylayout/"目錄下的.kl映射文件,來進行驗證哪個是有效的。映射方法:一,可以通過查看調(diào)用.kl的代碼。二,修改.kl文件來驗證。
在rk3066中,有效的映射文件是“rk29-keypad.kl”。在“rk29-keypad.kl”中添加以下代碼將wifi按鍵和Android中的“AVR_POWER按鍵”對應(yīng)。
key 186 AVR_POWER
說明:
key -- 是關(guān)鍵字。固定值,不需要改變。
186 -- wifi按鍵在linux驅(qū)動中對應(yīng)的鍵值,這里對應(yīng)KEY_F16的鍵值,即wifi對應(yīng)的按鍵值。關(guān)于linux驅(qū)動中的各個鍵值,可以查看“include/linux/input.h”
AVR_POWER -- wifi按鍵映射到Android中的按鍵,它對應(yīng)是“KeycodeLabels.h”文件中的KEYCODES表格元素的“l(fā)iteral”值。
KeycodeLabels.h中KEYCODES定義如下:
static const KeycodeLabel KEYCODES[] = { { "SOFT_LEFT", 1 }, { "SOFT_RIGHT", 2 }, { "HOME", 3 }, { "BACK", 4 }, { "CALL", 5 }, { "ENDCALL", 6 }, { "0", 7 }, { "1", 8 }, { "2", 9 }, { "3", 10 }, { "4", 11 }, { "5", 12 }, { "6", 13 }, { "7", 14 }, { "8", 15 }, { "9", 16 }, { "STAR", 17 }, { "POUND", 18 }, { "DPAD_UP", 19 }, { "DPAD_DOWN", 20 }, { "DPAD_LEFT", 21 }, { "DPAD_RIGHT", 22 }, { "DPAD_CENTER", 23 }, { "VOLUME_UP", 24 }, { "VOLUME_DOWN", 25 }, { "POWER", 26 }, { "CAMERA", 27 }, { "CLEAR", 28 }, { "A", 29 }, { "B", 30 }, { "C", 31 }, { "D", 32 }, { "E", 33 }, { "F", 34 }, { "G", 35 }, { "H", 36 }, { "I", 37 }, { "J", 38 }, { "K", 39 }, { "L", 40 }, { "M", 41 }, { "N", 42 }, { "O", 43 }, { "P", 44 }, { "Q", 45 }, { "R", 46 }, { "S", 47 }, { "T", 48 }, { "U", 49 }, { "V", 50 }, { "W", 51 }, { "X", 52 }, { "Y", 53 }, { "Z", 54 }, { "COMMA", 55 }, { "PERIOD", 56 }, { "ALT_LEFT", 57 }, { "ALT_RIGHT", 58 }, { "SHIFT_LEFT", 59 }, { "SHIFT_RIGHT", 60 }, { "TAB", 61 }, { "SPACE", 62 }, { "SYM", 63 }, { "EXPLORER", 64 }, { "ENVELOPE", 65 }, { "ENTER", 66 }, { "DEL", 67 }, { "GRAVE", 68 }, { "MINUS", 69 }, { "EQUALS", 70 }, { "LEFT_BRACKET", 71 }, { "RIGHT_BRACKET", 72 }, { "BACKSLASH", 73 }, { "SEMICOLON", 74 }, { "APOSTROPHE", 75 }, { "SLASH", 76 }, { "AT", 77 }, { "NUM", 78 }, { "HEADSETHOOK", 79 }, { "FOCUS", 80 }, { "PLUS", 81 }, { "MENU", 82 }, { "NOTIFICATION", 83 }, { "SEARCH", 84 }, { "MEDIA_PLAY_PAUSE", 85 }, { "MEDIA_STOP", 86 }, { "MEDIA_NEXT", 87 }, { "MEDIA_PREVIOUS", 88 }, { "MEDIA_REWIND", 89 }, { "MEDIA_FAST_FORWARD", 90 }, { "MUTE", 91 }, { "PAGE_UP", 92 }, { "PAGE_DOWN", 93 }, { "PICTSYMBOLS", 94 }, { "SWITCH_CHARSET", 95 }, { "BUTTON_A", 96 }, { "BUTTON_B", 97 }, { "BUTTON_C", 98 }, { "BUTTON_X", 99 }, { "BUTTON_Y", 100 }, { "BUTTON_Z", 101 }, { "BUTTON_L1", 102 }, { "BUTTON_R1", 103 }, { "BUTTON_L2", 104 }, { "BUTTON_R2", 105 }, { "BUTTON_THUMBL", 106 }, { "BUTTON_THUMBR", 107 }, { "BUTTON_START", 108 }, { "BUTTON_SELECT", 109 }, { "BUTTON_MODE", 110 }, { "ESCAPE", 111 }, { "FORWARD_DEL", 112 }, { "CTRL_LEFT", 113 }, { "CTRL_RIGHT", 114 }, { "CAPS_LOCK", 115 }, { "SCROLL_LOCK", 116 }, { "META_LEFT", 117 }, { "META_RIGHT", 118 }, { "FUNCTION", 119 }, { "SYSRQ", 120 }, { "BREAK", 121 }, { "MOVE_HOME", 122 }, { "MOVE_END", 123 }, { "INSERT", 124 }, { "FORWARD", 125 }, { "MEDIA_PLAY", 126 }, { "MEDIA_PAUSE", 127 }, { "MEDIA_CLOSE", 128 }, { "MEDIA_EJECT", 129 }, { "MEDIA_RECORD", 130 }, { "F1", 131 }, { "F2", 132 }, { "F3", 133 }, { "F4", 134 }, { "F5", 135 }, { "F6", 136 }, { "F7", 137 }, { "F8", 138 }, { "F9", 139 }, { "F10", 140 }, { "F11", 141 }, { "F12", 142 }, { "NUM_LOCK", 143 }, { "NUMPAD_0", 144 }, { "NUMPAD_1", 145 }, { "NUMPAD_2", 146 }, { "NUMPAD_3", 147 }, { "NUMPAD_4", 148 }, { "NUMPAD_5", 149 }, { "NUMPAD_6", 150 }, { "NUMPAD_7", 151 }, { "NUMPAD_8", 152 }, { "NUMPAD_9", 153 }, { "NUMPAD_DIVIDE", 154 }, { "NUMPAD_MULTIPLY", 155 }, { "NUMPAD_SUBTRACT", 156 }, { "NUMPAD_ADD", 157 }, { "NUMPAD_DOT", 158 }, { "NUMPAD_COMMA", 159 }, { "NUMPAD_ENTER", 160 }, { "NUMPAD_EQUALS", 161 }, { "NUMPAD_LEFT_PAREN", 162 }, { "NUMPAD_RIGHT_PAREN", 163 }, { "VOLUME_MUTE", 164 }, { "INFO", 165 }, { "CHANNEL_UP", 166 }, { "CHANNEL_DOWN", 167 }, { "ZOOM_IN", 168 }, { "ZOOM_OUT", 169 }, { "TV", 170 }, { "WINDOW", 171 }, { "GUIDE", 172 }, { "DVR", 173 }, { "BOOKMARK", 174 }, { "CAPTIONS", 175 }, { "SETTINGS", 176 }, { "TV_POWER", 177 }, { "TV_INPUT", 178 }, { "STB_POWER", 179 }, { "STB_INPUT", 180 }, { "AVR_POWER", 181 }, { "AVR_INPUT", 182 }, { "PROG_RED", 183 }, { "PROG_GREEN", 184 }, { "PROG_YELLOW", 185 }, { "PROG_BLUE", 186 }, { "APP_SWITCH", 187 }, { "BUTTON_1", 188 }, { "BUTTON_2", 189 }, { "BUTTON_3", 190 }, { "BUTTON_4", 191 }, { "BUTTON_5", 192 }, { "BUTTON_6", 193 }, { "BUTTON_7", 194 }, { "BUTTON_8", 195 }, { "BUTTON_9", 196 }, { "BUTTON_10", 197 }, { "BUTTON_11", 198 }, { "BUTTON_12", 199 }, { "BUTTON_13", 200 }, { "BUTTON_14", 201 }, { "BUTTON_15", 202 }, { "BUTTON_16", 203 }, { "LANGUAGE_SWITCH", 204 }, { "MANNER_MODE", 205 }, { "3D_MODE", 206 }, { "CONTACTS", 207 }, { "CALENDAR", 208 }, { "MUSIC", 209 }, { "CALCULATOR", 210 }, { "ZENKAKU_HANKAKU", 211 }, { "EISU", 212 }, { "MUHENKAN", 213 }, { "HENKAN", 214 }, { "KATAKANA_HIRAGANA", 215 }, { "YEN", 216 }, { "RO", 217 }, { "KANA", 218 }, { "ASSIST", 219 }, // 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, 0 } };
KeycodeLabels.h中的按鍵與Android框架層的KeyEvent.java中的按鍵值對應(yīng)。
例如:“AVR_POWER”對應(yīng)“KeyEvent.java中的鍵值”如下:
public static final int KEYCODE_AVR_POWER = 181;
這樣,我們發(fā)現(xiàn):將驅(qū)動的wifi按鍵就和Android系統(tǒng)中的KEYCODE_AVR_POWER就對應(yīng)起來了!
02, 在Android的framework層的鍵值處理函數(shù)中,捕獲按鍵,并進行相應(yīng)處理。
在framework層的input系統(tǒng)中,加入對wifi按鍵的捕獲。
添加的文件是:frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
添加的具體方法:在PhoneWindowManager.java的interceptKeyBeforeQueueing()函數(shù)中,捕獲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,并對其進行處理。
方案結(jié)論: 方案可行。而且運行效率比“方案二”高,不會造成功耗很大的問題。
最終總結(jié):方案三最好!