Android中外接鍵盤的檢測

今天來了一個問題:軟鍵盤沒法彈出。分析後是由於系統判斷當前有外接硬鍵盤,就會隱藏軟鍵盤。但實際狀況並非這麼簡單,該問題只有在特定條件下偶現,具體分析過程就不說了,就是軟硬鍵盤支持上的邏輯問題。藉着這個機會整理一下鍵盤檢測的過程。java

Configuration

Android系統中經過讀取Configuration中keyboard的值來判斷是否存在外接鍵盤。Configuration中關於鍵盤類型的定義以下,ide

public static final int KEYBOARD_UNDEFINED = 0; // 未定義的鍵盤
    public static final int KEYBOARD_NOKEYS = 1; // 無鍵鍵盤,沒有外接鍵盤時爲該類型
    public static final int KEYBOARD_QWERTY = 2; // 標準外接鍵盤
    public static final int KEYBOARD_12KEY = 3; // 12鍵小鍵盤

在最多見的狀況下,外接鍵盤未鏈接時keyboard的值爲KEYBOARD_NOKEYS,當檢測到鍵盤鏈接後會將keyboard的值更新爲KEYBOARD_QWERTY 。應用就能夠根據keyboard的值來判斷是否存在外接鍵盤,InputMethodService.java中有相似的判斷代碼。ui

// 軟件盤是否能夠顯示
    public boolean onEvaluateInputViewShown() {
        Configuration config = getResources().getConfiguration();
        return config.keyboard == Configuration.KEYBOARD_NOKEYS
                || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;
    }

如今的問題就轉向Configuration的keyboard是如何更新的。在WindowManagerService.java中,應用啓動時會更新Configuration,相關代碼以下。this

boolean computeScreenConfigurationLocked(Configuration config) {
        ......
        if (config != null) {
            // Update the configuration based on available input devices, lid switch,
            // and platform configuration.
            config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
            // 默認值爲KEYBOARD_NOKEYS
            config.keyboard = Configuration.KEYBOARD_NOKEYS;
            config.navigation = Configuration.NAVIGATION_NONAV;
            
            int keyboardPresence = 0;
            int navigationPresence = 0;
            final InputDevice[] devices = mInputManager.getInputDevices();
            final int len = devices.length;
            // 遍歷輸入設備
            for (int i = 0; i < len; i++) {
                InputDevice device = devices[i];
                // 若是不是虛擬輸入設備,會根據輸入設備的flags來更新Configuration
                if (!device.isVirtual()) {
                    ......
                    // 若是輸入設備的鍵盤類型爲KEYBOARD_TYPE_ALPHABETIC,則將keyboard設置爲KEYBOARD_QWERTY
                    if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
                        config.keyboard = Configuration.KEYBOARD_QWERTY;
                        keyboardPresence |= presenceFlag;
                    }
                }
            }
            ......
            // Determine whether a hard keyboard is available and enabled.
            boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
            // 更新硬件鍵盤狀態
            if (hardKeyboardAvailable != mHardKeyboardAvailable) {
                mHardKeyboardAvailable = hardKeyboardAvailable;
                mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
                mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
            }
            // 若是Setting中SHOW_IME_WITH_HARD_KEYBOARD被設置,將keyboard設置爲KEYBOARD_NOKEYS,讓軟件盤能夠顯示
            if (mShowImeWithHardKeyboard) {
                config.keyboard = Configuration.KEYBOARD_NOKEYS;
            }
            ......
        }

影響Configuration中keyboard的值有,lua

  • 默認值爲KEYBOARD_NOKEYS,表示沒有外接鍵盤。
  • 當輸入設備爲KEYBOARD_TYPE_ALPHABETIC時,更新爲KEYBOARD_QWERTY,一個標準鍵盤。
  • 當Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD爲1時,設置爲KEYBOARD_NOKEYS,目的是讓軟鍵盤能夠顯示。

inputflinger

接下來須要關注輸入設備時什麼時候被設置KEYBOARD_TYPE_ALPHABETIC的。搜索代碼能夠看到,這個flag實在native代碼中設置的,代碼在inputflinger/InputReader.cpp中。native和java使用了同必定義值,若是修改定義時須要注意同時修改。native中的名字爲AINPUT_KEYBOARD_TYPE_ALPHABETIC。code

InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
        const InputDeviceIdentifier& identifier, uint32_t classes) {
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
            controllerNumber, identifier, classes);
    ......
    if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
    }
    ......
    return device;
}

InputReader在增長設備時,根據classes的flag來設置鍵盤類型。這個flag又是在EventHub.cpp中設置的。orm

status_t EventHub::openDeviceLocked(const char *devicePath) {
    ......
    // Configure the keyboard, gamepad or virtual keyboard.
    if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) { 
        // 'Q' key support = cheap test of whether this is an alpha-capable kbd
        if (hasKeycodeLocked(device, AKEYCODE_Q)) {
            device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
        }
    ......
}

看到這裏就比較明確了,在EventHub加載設備時,若是輸入設備爲鍵盤,而且帶有'Q'鍵,就認爲這是一個標準的外接鍵盤。但爲什麼判斷'Q'鍵還不是很清楚。xml

keylayout

上面說道經過'Q'鍵來判斷是否爲外接鍵盤,這個'Q'鍵是Android的鍵值,鍵值是否存在是經過一個keylayout文件決定的。kl文件存儲在目標系統的/system/usr/keylayout/下,系統能夠有多個kl文件,根據設備的ID來命名。當系統加載鍵盤設備時,就會根據設備的Vendor ID和Product ID在/system/usr/keylayout/下尋找kl文件。例如一個kl文件名爲」Vendor_0c45_Product_1109.kl「,代表設備的Vendor ID爲0c45,Product ID爲1109。一個kl的內容示例以下,接口

key 1     BACK
key 28    DPAD_CENTER
key 102   HOME

key 103   DPAD_UP
key 105   DPAD_LEFT
key 106   DPAD_RIGHT
key 108   DPAD_DOWN

key 113   VOLUME_MUTE
key 114   VOLUME_DOWN
key 115   VOLUME_UP

key 142   POWER

鍵值映射須要使用關鍵之」key「進行聲明,以後跟着的數字爲Linux驅動中的鍵值定義,再後面的字符串是Android中按鍵的名稱。'Q'鍵是否存在徹底取決於kl文件中是否有映射,而不是實際物理鍵是否存在。kl文件的查找也是有一個規則的,其查找順序以下,rem

/system/usr/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl

/system/usr/keylayout/Vendor_XXXX_Product_XXXX.kl

/system/usr/keylayout/DEVICE_NAME.kl

/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX_Version_XXXX.kl

/data/system/devices/keylayout/Vendor_XXXX_Product_XXXX.kl

/data/system/devices/keylayout/DEVICE_NAME.kl

/system/usr/keylayout/Generic.kl

/data/system/devices/keylayout/Generic.kl

同時支持軟硬鍵盤

有了上面的知識,就能夠給出同時支持軟硬鍵盤的方案。

  • 修改源碼邏輯,設置Configuration中keyboard的值爲KEYBOARD_NOKEYS。這種Hack其實很差,破壞原生邏輯,缺少移植性。非要這樣改的話,能夠增長對設備的判斷,只有特定的鍵盤設備設置爲KEYBOARD_NOKEYS,減小反作用。
  • 修改keylayout,去掉'Q'鍵映射。有時kl文件寫的不標準,爲了通用把全部鍵的映射都寫上了,實際硬件鍵卻不多,咱們就是這種狀況。應該按照真實硬件來編寫kl文件。
  • 設置Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD爲1。我認爲這是最標準的修改方式,也很是方便。

關於第三個方案的修改方式有兩種,一種是修改缺省的setting值,在文件frameworks/base/packages/SettingsProvider/res/values/defaults.xml中增長,

<integer name="def_show_ime_with_hard_keyboard">1</integer>

另外一種方式是在系統啓動時在代碼中經過接口進行設置。

Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1);
相關文章
相關標籤/搜索