萬盛學電腦網

 萬盛學電腦網 >> 網絡編程 >> 安卓開發 >> Android按鍵添加和處理的三個解決

Android按鍵添加和處理的三個解決

 實現方案需求: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狀態,是全局的。

架構圖:

Android按鍵添加和處理的三個解決 三聯

 

具體實現:
通過驅動,將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  = 4,
        .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 = 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;
        }   
        // 同步按鍵狀態
        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=%dn", __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, "%dn", 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, "%dn", &val))) {
        printk("%s errorn", __func__); 
        return  -EINVAL; 
    }     

    if(!val) {
        combo_module.status_wifikey = 0;
    } else {
        combo_module.status_wifikey = 1;
    }
    printk("%s status_wifikey=%dn", __func__, combo_module.status_wifikey);

    return 0;
}

// 將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_IRWX

copyright © 萬盛學電腦網 all rights reserved