1. 程式人生 > >Android源碼開發筆記 軟鍵盤與內置物理鍵盤共存以及外接藍牙鍵盤不共存邏輯

Android源碼開發筆記 軟鍵盤與內置物理鍵盤共存以及外接藍牙鍵盤不共存邏輯

icon eva boolean 功能實現 小鍵盤 nokey top apps 需求

需求1: android設備自帶九鍵的小鍵盤,此時小鍵盤被識別為HW Keyboard,默認與軟鍵盤不能共存,需要使軟鍵盤與物理鍵盤共存。

實現:

在網上找的別人總結的Android5.1的解決方法,需要解決的codebase為Android6.0,都可以用。

方法一:(此方法在Android8.0 codebase已不可用) frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java中,如果把updateShowImeWithHardKeyboard()方法中的showImeWithHardKeyboard變量直接置為true,則可以實現軟鍵盤與物理鍵盤的同時使用。(原本為讀取Setting數據庫字段來判斷,所以也可以直接修改Setting字段來實現)

    public void updateShowImeWithHardKeyboard() {
        synchronized (mWindowMap) {
            final boolean showImeWithHardKeyboard = Settings.Secure.getIntForUser(
                        mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0,
                        mCurrentUserId) 
== 1;if (mShowImeWithHardKeyboard != showImeWithHardKeyboard) { mShowImeWithHardKeyboard = showImeWithHardKeyboard; mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } } }

方法二:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java,修改onEvaluateInputViewShown()方法直接返回true

public boolean onEvaluateInputViewShown() {
    Configuration config = getResources().getConfiguration();
    //return config.keyboard == Configuration.KEYBOARD_NOKEYS
    //      || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;
    return  true;
}

方法三:在外部需要修改的合適位置,直接將Setting字段修改掉,隨時可以改回來,一勞永逸。

Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1);// 1: Disabled,0: Enabled

需求2:軟鍵盤與內置物理鍵盤共存基礎上,再外接藍牙鍵盤。此時外接藍牙鍵盤與軟鍵盤不可共存。

分析:此時外接鍵盤也被視為物理鍵盤,按照上面需求1的方法修改後,使藍牙鍵盤與軟鍵盤是共存的,所以不符合需求。需要找到藍牙鍵盤連接上的位置,來把前面需求1改的地方取消掉。

即:未連接藍牙鍵盤時,將Setting字段改為共存模式,使軟鍵盤與內置物理鍵盤共存;連接藍牙鍵盤後,將Setting字段改為不共存模式,使軟鍵盤與藍牙鍵盤不共存(此時藍牙鍵盤與內置物理鍵盤是共存的)。

實現:本需求的難點為找到恰當的位置來修改Setting字段,此恰當位置為:藍牙鍵盤連接成功或斷開,以及藍牙關閉。

實現分析流程:

1. 通過Settings中Bluetooth Settings部分可以看到,連接藍牙鍵盤時,藍牙設備的類型已經可以通過圖標區分出來。這樣就不用懷疑是否能夠區分藍牙鍵盤和其他藍牙設備了。

在packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDevicePreference.java中根據icon可以看到,不同種類的BT device已經可以區分了。

private int getBtClassDrawable() {
        BluetoothClass btClass = mCachedDevice.getBtClass();
        if (btClass != null) {
            switch (btClass.getMajorDeviceClass()) {
                case BluetoothClass.Device.Major.COMPUTER:
                    return R.drawable.ic_bt_laptop;

                case BluetoothClass.Device.Major.PHONE:
                    return R.drawable.ic_bt_cellphone;

                case BluetoothClass.Device.Major.PERIPHERAL:
                    return HidProfile.getHidClassDrawable(btClass);

                case BluetoothClass.Device.Major.IMAGING:
                    return R.drawable.ic_bt_imaging;

                default:
                    // unrecognized device class; continue
            }
        } else {
            Log.w(TAG, "mBtClass is null");
        }
...

需要的藍牙鍵盤在 HidProfile.getHidClassDrawable(btClass)獲得。

frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java

public static int getHidClassDrawable(BluetoothClass btClass) {
        switch (btClass.getDeviceClass()) {
            case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
            case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
                return R.drawable.ic_lockscreen_ime;
            case BluetoothClass.Device.PERIPHERAL_POINTING:
                return R.drawable.ic_bt_pointing_hid;
            default:
                return R.drawable.ic_bt_misc_hid;
        }
    }

2. 通過抓系統藍牙profile log來確認斷開連接藍牙鍵盤時,會在framework裏面的Bluetooth的哪邊被trigger到。

adb logcat |grep Profile

在連接/斷開藍牙鍵盤時,可以抓到下面的log:

kunkka@kunkka-Lenovo:~$ adb logcat |grep Profile
12-22 03:55:00.524  1257  1257 I SystemServer: SamplingProfiler Service
12-22 04:29:41.923  3916  4051 D CachedBluetoothDevice: onProfileStateChanged: profile HID newProfileState 0
12-22 06:25:59.666  7284  7284 D CachedBluetoothDevice: onProfileStateChanged: profile HID newProfileState 2
12-22 06:25:59.668  3916  4051 D CachedBluetoothDevice: onProfileStateChanged: profile HID newProfileState 2
12-22 06:26:33.245  7284  7284 D CachedBluetoothDevice: onProfileStateChanged: profile HID newProfileState 0
12-22 06:26:33.245  3916  4051 D CachedBluetoothDevice: onProfileStateChanged: profile HID newProfileState 0
12-22 06:26:45.586  7284  7284 D CachedBluetoothDevice: onProfileStateChanged: profile HID newProfileState 2
12-22 06:26:45.587  3916  4051 D CachedBluetoothDevice: onProfileStateChanged: profile HID newProfileState 2
...

可以定位到frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java

在onProfileStateChanged方法中可以看到,藍牙連接和斷開時都可以走到!而且去獲得藍牙設備類型的context也都有!這樣問題解決一大半!

在onProfileStateChanged方法中添加藍牙連接和斷開時的設備類型判斷:

            if(mBtClass.getMajorDeviceClass() == BluetoothClass.Device.Major.PERIPHERAL
                    && (mBtClass.getDeviceClass() == BluetoothClass.Device.PERIPHERAL_KEYBOARD
                    || mBtClass.getDeviceClass() == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING)) {
                Log.i("Kunkka0", "getMajorDeviceClass = Peripheral & getDeviceClass = KeyBoard");
                if(newProfileState == 0) {// Disconnected
                    Log.i("Kunkka0","SET SHOW_IME_WITH_HARD_KEYBOARD TO 1");
                    Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1);
                }else {
                    Log.i("Kunkka0","SET SHOW_IME_WITH_HARD_KEYBOARD TO 0");
                    Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0);
                }
            }else {
                Log.i("Kunkka0","getMajorDeviceClass() = "+mBtClass.getMajorDeviceClass()+", getDeviceClass = "+mBtClass.getDeviceClass());
                Log.i("Kunkka0","PERIPHERAL = 1280, PERIPHERAL_KEYBOARD = 1344, PERIPHERAL_KEYBOARD_POINTING = 1472");
            }

加完編譯出image來測試,功能實現!

但是,在關閉藍牙時,藍牙鍵盤就不能用了,但是此處不能收到狀態變化!此時,關閉藍牙,軟鍵盤和藍牙鍵盤都不能用了!

3. 為了解決關閉藍牙時的問題,再去搜索藍牙開關狀態變化的事件:

在frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java中可以看到有:

    void onBluetoothStateChanged(int bluetoothState);
    void onScanningStateChanged(boolean started);
    void onDeviceAdded(CachedBluetoothDevice cachedDevice);
    void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
    void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
    void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state);

查看所有重載此事件的class,Open implementation-可以看到:

技術分享圖片

下面幾個都是Settings中的,但我們的需求不止在Settings中,所以看上面KeyboardUI和systemui下面的statusbar。

StatusBar下面的BluetoothControllerImpl比較符合。

打開frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java

除了onBluetoothStateChanged外還有個updateConnected()方法,居然也可以偵測藍牙設備連接狀態!

所以我們只需要在onBluetoothStateChanged()和updateConnected()中都加入事件偵測來修改Settings字段就可以了!

4. 最終解決辦法:

a. 在onBluetoothStateChanged()中,當藍牙關閉時,使鍵盤共存,保證軟鍵盤和內置物理鍵盤的共存。

    @Override
    public void onBluetoothStateChanged(int bluetoothState) {
        mEnabled = bluetoothState == BluetoothAdapter.STATE_ON;
    
        if(!mEnabled) {
            Log.i("Kunkka0","onBluetoothStateChanged: SET SHOW_IME_WITH_HARD_KEYBOARD TO 1");
            Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1);
        }

        mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED);
    }

b. 在updateConnected()中,當藍牙鍵盤連接時,使軟硬鍵盤不共存,藍牙鍵盤斷開時,使軟硬件盤共存。

private void updateConnected() {
        ...
        for (CachedBluetoothDevice device : getDevices()) {
            if (device.isConnected()) {
                mLastDevice = device;
            }

            if(device.getBtClass().getMajorDeviceClass() == BluetoothClass.Device.Major.PERIPHERAL
                    && (device.getBtClass().getDeviceClass() == BluetoothClass.Device.PERIPHERAL_KEYBOARD
                    || device.getBtClass().getDeviceClass() == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING)) {
                Log.i("Kunkka0", "getMajorDeviceClass = Peripheral & getDeviceClass = KeyBoard");
                if(!device.isConnected()) {// Disconnected
                    Log.i("Kunkka0","SET SHOW_IME_WITH_HARD_KEYBOARD TO 1");
                    Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1);
                }else {
                    Log.i("Kunkka0","SET SHOW_IME_WITH_HARD_KEYBOARD TO 0");
                    Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0);
                }
            }else {
                Log.i("Kunkka0","getMajorDeviceClass() = "+device.getBtClass().getMajorDeviceClass()+", getDeviceClass = "+device.getBtClass().getDeviceClass());
                Log.i("Kunkka0","PERIPHERAL = 1280, PERIPHERAL_KEYBOARD = 1344, PERIPHERAL_KEYBOARD_POINTING = 1472");
            }
            
        }
        ...
    }

測試,驗證通過!

Android源碼開發筆記 軟鍵盤與內置物理鍵盤共存以及外接藍牙鍵盤不共存邏輯