Android中外接键盘的检测的实现

作者:戈壁老王 时间:2023-07-27 21:15:13 

今天来了一个问题:软键盘无法弹出。分析后是因为系统判断当前有外接硬键盘,就会隐藏软键盘。但实际情况并不是这么简单,该问题只有在特定条件下偶现,具体分析过程就不说了,就是软硬键盘支持上的逻辑问题。借着这个机会整理一下键盘检测的过程。

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

来源:https://segmentfault.com/a/1190000021080958

标签:Android,外接键盘,检测
0
投稿

猜你喜欢

  • 全面解析Hibernate关联操作、查询操作、高级特性、并发处理机制

    2021-06-25 08:48:48
  • 使用java生成json时产生栈溢出错误问题及解决方案

    2023-01-09 17:41:10
  • Android Loop机制中Looper与handler详细分析

    2023-01-13 04:40:56
  • android照相、相册获取图片剪裁报错的解决方法

    2021-11-21 20:18:14
  • Kotlin Option与Either及Result实现异常处理详解

    2022-03-31 17:33:18
  • Java代码实现微信页面滚动防露底(核心代码)

    2023-11-10 13:47:08
  • 解读在C#中winform程序响应键盘事件的详解

    2023-10-08 09:17:00
  • SVN报错:Error Updating changes:svn:E155037的解决方案

    2023-06-11 07:27:11
  • Kotlin基础教程之伴生对象,getter,setter,内部,局部,匿名类,可变参数

    2021-08-05 03:56:02
  • C#客户端程序Visual Studio远程调试的方法详解

    2022-10-20 19:13:54
  • Java Swing JButton按钮的实现示例

    2023-05-05 00:25:43
  • 基于Android SQLite的升级详解

    2021-06-24 05:26:05
  • java之使用多线程代替for循环(解决主线程提前结束问题)

    2021-11-21 01:23:55
  • C#实现随机数产生类实例

    2021-11-16 15:46:49
  • Java回调函数与观察者模式实例代码

    2023-11-16 17:30:11
  • Android设备与外接U盘实现数据读取操作的示例

    2023-06-18 13:35:00
  • Java常量池知识点总结

    2023-01-09 10:23:09
  • C语言时间函数之strftime()详解

    2023-06-26 02:42:32
  • Android 解决ScrollView嵌套CridView显示问题

    2021-11-14 02:22:00
  • C# 获取当前年份的周期及周期所在日期范围(推荐)

    2021-10-06 15:00:44
  • asp之家 软件编程 m.aspxhome.com