會議平板按鍵相關

會議平板的按鍵加載會在多個不同地方註冊及上報按鍵,需要逐個去解析。

  • 遙控
  • 五向
  • 廣播

一.從遙控器獲得按鍵

1.設備對應keylayout映射文件

1) 佈局文件保存路徑

遙控器的KeyMap具體kl文件保存路徑爲device/whaley/generic/prebuilts/keylayout目錄下保存。kl文件的保存格式爲Vendor_[vendorID]_Product_[productID].kl** (e.g.) Vendor_2ba5_Product_0882.kl**
查看藍牙設備的Vendor ID和Product ID
adb shell cat /proc/bus/input/devices
(e.g.)

I: Bus=0005 Vendor=2ba5 Product=0882 Version=0100
N: Name="微鯨遙控器"
P: Phys=
S: Sysfs=/devices/virtual/misc/uhid/0005:2BA5:0882.0005/input/input9
U: Uniq=dc:2c:26:fa:6e:a2
H: Handlers=sysrq kbd event5
B: PROP=0
B: EV=12001b
B: KEY=1000002000087 ff9f387ad9415fff febeffdfffefffff fffffffffffffffe
B: ABS=10000000000
B: MSC=10
B: LED=1f

2) 源碼按鍵佈局選擇

開始從frameworks/native/libs/input/InputDevice.cpp中的getInputDeviceConfigurationFilePathByDeviceIdentifier方法得到佈局位置

String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
        const InputDeviceIdentifier& deviceIdentifier,
        InputDeviceConfigurationFileType type) {
    if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
        if (deviceIdentifier.version != 0) {
            // Try vendor product version.
            String8 versionPath(getInputDeviceConfigurationFilePathByName(
                    String8::format("Vendor_%04x_Product_%04x_Version_%04x",
                            deviceIdentifier.vendor, deviceIdentifier.product,
                            deviceIdentifier.version),
                    type));
            if (!versionPath.isEmpty()) {
                return versionPath;
            }
        }

        // Try vendor product.
        String8 productPath(getInputDeviceConfigurationFilePathByName(
                String8::format("Vendor_%04x_Product_%04x",
                        deviceIdentifier.vendor, deviceIdentifier.product),
                type));
        if (!productPath.isEmpty()) {
            return productPath;
        }
    }

    // Try device name.
    return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
}

從productPath方法得到的按鍵佈局位置/system/usr/keylayout/,再通過frameworks/native/services/inputflinger/EventHub.cpp中的loadConfigurationLocked去解析文件內容

void EventHub::loadConfigurationLocked(Device* device) {
    device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
            device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
    if (device->configurationFile.isEmpty()) {
        ALOGD("No input device configuration file found for device '%s'.",
                device->identifier.name.string());
    } else {
        status_t status = PropertyMap::load(device->configurationFile,
                &device->configuration);
        if (status) {
            ALOGE("Error loading input device configuration file for device '%s'.  "
                    "Using default configuration.",
                    device->identifier.name.string());
        }
    }
}

3) 按鍵佈局文件解讀

Android配置藍牙鍵值的步驟和一般的鍵值基本相同,但是有幾點區別需要注意。我們在驅動層用多個紅外鍵值對應了一個Android鍵值,在應用層靠KeyEvent.getScanCode()函數獲取linux鍵值來加以區分。紅外這麼做已經比較熟,有方案商提供的文檔。但是藍牙用的是Linux默認的一套流程,需要花時間去搞。
首先先看下kl文件的差別
(e.g.)

#格式爲基本key event上報
key 103   DPAD_UP
#格式爲Bluetooth key event 上報 第一列表示這是一個鍵值,第二列可以先不管,第三列是藍牙鍵值,第四列是Android鍵名
key usage 0x000700ea WHALEYMIC

2.會議平板的鍵值上報策略

並非通過event上報遙控器鍵值,經過跟蹤源碼發現而是通過驅動層直接回調方法給FWK層,去模擬按鍵觸發,接下來一起看下按鍵流程吧。
按鍵流程是一致的但是調用的方法不同,都是異曲同工,這裏就介紹分享語音鍵的上層上報流程

1) 設置驅動層BT監聽註冊回調

驅動層device/hisilicon/bigfish/bluetooth/[藍牙設備號]/bluedroid/btif/src/btif_hh.c實現註冊監聽

static bt_status_t init( bthh_callbacks_t* callbacks )
{
    UINT32 i;
    BTIF_TRACE_EVENT("%s", __FUNCTION__);

    bt_hh_callbacks = callbacks;
    memset(&btif_hh_cb, 0, sizeof(btif_hh_cb));
    for (i = 0; i < BTIF_HH_MAX_HID; i++){
        btif_hh_cb.devices[i].dev_status = BTHH_CONN_STATE_UNKNOWN;
    }
    /* Invoke the enable service API to the core to set the appropriate service_id */
    btif_enable_service(BTA_HID_SERVICE_ID);
    return BT_STATUS_SUCCESS;
}

2) Native層與kernel層交互

com_android_bluetooth_hid.cpp
Native層註冊按鍵回調,創建sBluetoothHidInterface接口並初始化sBluetoothHidCallbacks

static void initializeNative(JNIEnv *env, jobject object) {
    const bt_interface_t* btInf;
    bt_status_t status;

    if ( (btInf = getBluetoothInterface()) == NULL) {
        ALOGE("Bluetooth module is not loaded");
        return;
    }

    if (sBluetoothHidInterface !=NULL) {
        ALOGW("Cleaning up Bluetooth HID Interface before initializing...");
        sBluetoothHidInterface->cleanup();
        sBluetoothHidInterface = NULL;
    }

    if (mCallbacksObj != NULL) {
        ALOGW("Cleaning up Bluetooth GID callback object");
        env->DeleteGlobalRef(mCallbacksObj);
        mCallbacksObj = NULL;
    }

    if ( (sBluetoothHidInterface = (bthh_interface_t *)
          btInf->get_profile_interface(BT_PROFILE_HIDHOST_ID)) == NULL) {
        ALOGE("Failed to get Bluetooth HID Interface");
        return;
    }

    if ( (status = sBluetoothHidInterface->init(&sBluetoothHidCallbacks)) != BT_STATUS_SUCCESS) {
        ALOGE("Failed to initialize Bluetooth HID, status: %d", status);
        sBluetoothHidInterface = NULL;
        return;
    }

    mCallbacksObj = env->NewGlobalRef(object);
}

與JNI與JAVA賦值

method_onAudioStateChanged = env->GetMethodID(clazz, "onAudioStateChanged", "([BII)V");

回調Java的方法

static void audio_state_callback(bt_bdaddr_t *bd_addr, bthh_audio_state_t state,
                                    bthh_audio_type_t audio_type) {
    jbyteArray addr;

    CHECK_CALLBACK_ENV
    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (!addr) {
        ALOGE("Fail to new jbyteArray bd addr for HID channel state");
        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
        return;
    }
    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);

    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, addr, (jint) state,
                                                (jint) audio_type);
    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
    sCallbackEnv->DeleteLocalRef(addr);
}

3) FWK層方法調用

HidService.java
調用onAudioStateChanged的方法啓動handle實現模擬按鍵的操作

final long downTime = SystemClock.uptimeMillis();
KeyEvent down = KeyEvent.obtain(downTime, downTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_WHALEYMIC, 0, 0,KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD, null);
InputManager.getInstance().injectInputEvent(down, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
down.recycle();
HidService\nFWKcom_android\n_blcom_android\n_bluebtif_hh\nkernelaudio_state_callback()initNative()init()註冊audio_state_callback()onAudioStateChanged()HidService\nFWKcom_android\n_blcom_android\n_bluebtif_hh\nkernel遙控器按鍵註冊上報流程圖

二.從五向鍵獲得按鍵

待更新

三.通過廣播來下發按鍵

上層的按鍵邏輯下發並不是全部由系統按鍵流程下發,會議平板會做一層的監聽,但不會攔截,在InputManagerService類的interceptKeyBeforeQueueing的系統攔截處增加的一個監聽器,應用需要在監聽器中註冊,從而得到相關按鍵上報.
應用註冊的統一入口,得到InputManagerService服務,註冊按鍵監聽器InputManager.java

    public void registerListener(IWhaleyKeyListener listener, String id, boolean reg) { 
        try {
            mIm.registerListener(listener, id, reg);
        }
        catch (RemoteException ex) {
            Log.w(TAG, "Failed to registerListener", ex);
        }
    }

註冊監聽位置InputManagerService.java

    private RemoteCallbackList<IWhaleyKeyListener> mCallbacks;

    //由InputManager傳遞,處理其他應用的監聽入口,需要參數監聽器實例,記錄索引的ID約定爲應用名ID,boolean是否監聽或取消監聽
    @Override
    public void registerListener(IWhaleyKeyListener listener, String id, boolean reg) {
        synchronized (mCallbacks) {
            if(reg && listener != null) {
                mCallbacks.register(listener, id);
            }
            else if(!reg && listener != null){
                mCallbacks.unregister(listener);
            }
        }
    }

監聽的回調

    private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        globalCallBack(event, policyFlags);
        return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
    }

枚舉監聽者發送得到的按鍵鍵值

    private int globalCallBack(KeyEvent event, int policyFlags) {
        int ret = 0;

        synchronized (mCallbacks) {
            if(mCallbacks.getRegisteredCallbackCount() > 0) {
                int n = mCallbacks.beginBroadcast();
                try {
                    if(n > 0) {
                        for (int i = 0; i < n; i++) {
                            String id = (String)mCallbacks.getBroadcastCookie(i);
                            ret = mCallbacks.getBroadcastItem(i).notify(event, String.valueOf(policyFlags));
                            //Log.d(TAG, "#mCallbacks.notify:" + id);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                mCallbacks.finishBroadcast();
            }
        }

        return ret;
    }

(e.g.)應用的註冊按鍵監聽的方式

private InputManager mInputManager;
private MyKeyListener mMyKeyListener;

mMyKeyListener = new MyKeyListener();
mInputManager = InputManager.getInstance();
mInputManager.registerListener(mMyKeyListener, this.getPackageName(), true);

private class MyKeyListener extends IWhaleyKeyListener.Stub { 
    public MyKeyListener() {
    }

    @Override
    public int notify(KeyEvent event, String extra) throws RemoteException {
        .......
        return 0;
    }
}

End

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章