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


猜你喜欢
- 生产者消费者模式的几种实现方式拿我们生活中的例子来说,工厂生产出来的产品总是要输出到外面使用的,这就是生产与消费的概念。在我们实际的软件开发
- 本文实例为大家分享了C#实现会移动的文字效果的具体代码,供大家参考,具体内容如下1 题目描述(1)Form1窗体设计界面如下:(2)窗体左侧
- 本文实例讲述了Android开发使用Handler的PostDelayed方法实现图片轮播功能。分享给大家供大家参考,具体如下:第一步:创建
- 1.一段java程序是如何运行起来的呢?Java源文件,通过编译器,产生.Class字节码文件,字节码文件通过Java虚拟机中的解释器,编译
- 下载:1.在spring-mvc中配置(用于100M以下的文件下载)<bean class="org.springframe
- 用C#写的一个DVD管理器,供大家参考,具体内容如下(大神勿喷,初学者以借鉴为主)一共分为三个类分别是:DVD(启动类),XinXi(信息类
- Android之文件数据存储一、文件保存数据介绍Activity提供了openFileOutput()方法可以用于把数据输出到文件中,具体的
- 前言上节在谈论Bean的实例化过程时,在说明实例化后阶段时只是粗略地看了一下populateBean,并未展开分析。本节接着populate
- ViewModel的创建方式在我们项目中, 引入了viewModel 做MVI 设计模式的组成部分,它是JetPack 组件库中的重要成员。
- 1. json数据类型类型描述Number数字型String字符串型Boolean布尔型Array数组Object对象null空值(1)js
- Android自带的跑马灯效果不太好控制,还必须要满足条件才能有效果,而且速度不受控制。前面我的博客中有一篇就是用Android自带的跑马灯
- 最近在通讯录新建联系人=中,一进入一个页面, EditText默认就会自动获取焦点,很是郁闷, 如何让EditText不自动获取焦点?那么如
- 对openfeign不清楚的同学可以参考下我的这篇文章:springboot~openfeign从此和httpClient说再见对于open
- 我就废话不多说了,大家还是直接看代码吧!public static String mapToTxt(Map<String,String
- 在定义API的时候,对于一些返回集合对象的方法,很多人喜欢将返回类型定义成IEnumerable<T>,这本没有什么问题。这里要
- 在Java解析XML文件的过程中,有时需要获取符合某些特定条件的节点,以下是实现代码。import javax.xml.xpath.XPat
- 1 框架组成SpringSpringMVCMyBatis2 所需工具Mysql 8.0.15数据库管理系统,创建数据库Tomcat 8.5.
- 一、简介1.什么是GUID?全局唯一标识符(GUID,Globally Unique Identifier),GUID也称作 UUID(Un
- 1 依赖配置<parent> <groupId>org.springframework.b
- Android UI中TextView的使用方法一、TextView不同区域设置颜色,大小、点击事件String msg = getReso