1. 程式人生 > 程式設計 >Android中外接鍵盤的檢測的實現

Android中外接鍵盤的檢測的實現

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

Configuration

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

  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中有類似的判斷程式碼。

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

現在的問題就轉向Configuration的keyboard是如何更新的。在WindowManagerService.java中,應用啟動時會更新Configuration,相關程式碼如下。

  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的值有,

  • 預設值為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。

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中設定的。

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'鍵還不是很清楚。

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檔案的查詢也是有一個規則的,其查詢順序如下,

/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);

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。