一、Android 官方定义:

用于描述输入设备的信息、性质、功能。

每个输入设备可以支持多类输入,例如,多功能键盘可以将标准键盘的功能与触控板鼠标或其他定点设备组合在一起。

一些输入设备可以呈现出多个可区分的输入源,比如你的USB Dongle鼠标,它可能具备键盘、鼠标、游戏摇杆的性质。

二、Android 应用层关联的API:

// 输入设备管理类:
android.hardware.input.InputManager// 输入设备的监听器:用于监听 USB 设备的热插拔、信号变化
android.hardware.input.InputManager$InputDeviceListener// 输入设备的描述类:
android.view.InputDevice

三、Android USB 输入设备的软件架构:

四、对于客户端开发而言,只要关注 IInputManager.aidl 的接口定义,通过 InputManager/InputDevice 的API调用即可完成基本的功能开发,可以在线访问:IInputManager.aidl Android 9.0 接口线上访问地址。

五、客户需求:参考样机 MTK9632(7007)样机,对于插入的设备进行检测,并弹出对应提示(无法给出具体的SPEC、行为逻辑)。

根据上面需求,红庆对样机做了基本的实验,运用了USB键盘、USB鼠标设备,观察到如下现象:

  1. 插入 USB 键盘,提示 USB 键盘已插入,拔掉该设备,提示 USB 键盘已删除。
  2. 插入 USB 鼠标,提示 USB 鼠标已插入,拔掉该设备,提示 USB 鼠标已删除。

于是,形成了早起的需求,并根据对API的理解,写下了如下代码:

各个函数/字段大意:

  • init:客户端实例化 InputManager,通过向其注册 InputDeviceListener 来动态监听设备的插入、拔出、设备属性变化等信息:
  • release:客户端将 InputDeviceListener 从 InputManager 中解除注册;
  • mInputDeviceListener:实现了 InputManager.InputDeviceListener 接口的对象,用于注册进 InputManager,来观察输入设备的热插拔和性质变化。
  • mInputDeviceListener#onInputDeviceAdded:输入设备键入Android Host完成供电并且可以交互的回调,简单理解为设备插入。
  • mInputDeviceListener#onInputDeviceRemoved:输入设备从Android Host交出交互传输的回调,简单理解为设备拔除。
  • mInputDeviceListener#onInputDeviceChanged:输入设备在工作过程中,某些性质发生变化的回调。
private InputManager mInputManager;
private Context mContext;
private Map<Integer, InputDevice> mDeviceMap = new HashMap<>();
private InputManager.InputDeviceListener mInputDeviceListener = new InputManager.InputDeviceListener() {@Overridepublic void onInputDeviceAdded(int deviceId) {// 当设备插入时,回调此函数InputDevice device = mInputManager.getInputDevice(deviceId);if (device == null) {return;}if (isKeyBoard(device)) {mDeviceMap.put(deviceId, device);showToast("USB键盘已连接");} else if (isMouse(device)) {mDeviceMap.put(deviceId, device);showToast("USB鼠标已连接");}}@Overridepublic void onInputDeviceRemoved(int deviceId) {// 当设备移除时,回调此函数(设备移除时,无法获取到USB设备的信息,所以上面的MAP就起到了作用)InputDevice device = mDeviceMap.remove(deviceId);if (device == null) {return;}if (isKeyBoard(device)) {mDeviceMap.put(deviceId, device);showToast("USB键盘已删除");} else if (isMouse(device)) {mDeviceMap.put(deviceId, device);showToast("USB鼠标已删除");} }@Overridepublic void onInputDeviceChanged(int deviceId) {// 当设备性质发生动态变化时,回调此函数}
}void init(Context context) {this.mContext = context;// 1. 实例化输入管理器,基于 Binder 的调用,通过 ServiceManager 向 SystemServer 进程发起服务别名为“input”的服务查询,略mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);// 2. 向输入管理器中注册设备监听器,Native 层的 InputReader.cpp 识别到设备变化,向 SystemServer 进程发送刷列表的消息,notifyInputDevicesChanged,收到消息后,向Binder客户端回传设备信息。mInputManager.registerInputDeviceListener(mInputDeviceListener);
}void release(Context context) {// 移除设备管理器中的设备监听器mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
}void showToast(String content) {Toast.makeText(mContext, content, Toast.LENGTH_SHORT).show();
}boolean isKeyBoard(InputDevice device) {int sourceMask = device.getSources();return sourceMask & InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD;
}boolean isMouse(InputDevice device) {int sourceMask = device.getSources();return sourceMask & InputDevice.SOURCE_MOUSE == InputDevice.SOURCE_MOUSE;
}

根据如上信息,算是大致完成了需求,但在客户测试过程中,报出了非常多的问题,这里摘主要的:

  1. 插入 USB 耳机,提示 USB 键盘已连接;拔掉 USB 耳机,提示 USB 键盘已删除;
  2. 插入 USB 鼠标,提示 USB 键盘已连接;拔掉 USB 鼠标,提示 USB 键盘已删除;

经过不断和客户沟通,他们仍无法提供具体的需求 SPEC、测试设备清单、甚至是软件行为逻辑给到我们。由于和客户是异地协作,我们制作软件,他们测试,所以借设备是不可能(之前出差在客户现场支持,借设备也很难)。

但确认问题这个过程,我们依然要做,在条件有限的情况下,我们也只能做这么几件事:

其一,咨询其测试设备的品牌、型号,在公司内寻找,必要时可以申请购买;

其二,了解输入设备类型知识,尽量多借一些不同品类、同品类不同型号的设备,对客户样机进行把玩,并且做好记录。

其三,客户样机具备 adb Debug 能力,从里面打捞出客户的应用软件,进行反向解析,看看能不能找到其他额外的信息。

通过第一个动作,迅速将两个问题的相关信息和范围进行了锁定:

  • 问题1:USB Dongle 耳机品牌为:UGREEN绿联,型号为:US205,插入该设备,样机设备无任何提示,拔出也是一样的。
  • 问题2:USB 鼠标品牌为:罗技,信号为:G304,插入该设备,应该提示为:USB 鼠标已连接,拔出时提示:USB 鼠标已删除。

通过第二个动作,将整理成的记录变为了需求点,从而将模糊的需求,明确到了这种程度:

  1. 插入 USB 键盘,提示 USB 键盘已插入,拔掉该设备,提示 USB 键盘已删除。
  2. 插入 USB 鼠标,提示 USB 鼠标已插入,拔掉该设备,提示 USB 鼠标已删除。
  3. 插入 USB Dongle 耳机,参考样机逻辑(有的情况不提示,有的情况提示键盘...),存在兼容性问题。
  4. 隐性需求1:开机时,如果已经插入了 USB 输入设备,参考样机行为;STR 关开机之前如果已经插入了 USB 输入设备,参考样机行为;
  5. 隐性需求2:客户无法提供 Audio 类产品的行为逻辑;也无法提供需要兼容设备的类型和设备列表;我们通过不同设备进行模拟,大致确认了客户只需兼容肉眼可识别的鼠标、键盘。
  6. 隐性需求3:同类型同型号的设备兼容考虑,主要是同时插拔的场景。客户样机只有一个 USB 口可用,通过 USB 拓展坞观察。

通过第三个动作,成功的找到了如下有用的信息:

  1. 成功的找到了客户软件里面的 UI 资源包、APK主逻辑包
  2. 成功的对主逻辑包进行了反向编译,并且发现客户有对一些特殊设备做过滤处理,大致分了三大类:Audio 类设备、部分USB Dongle 设备、P客户特定的遥控器设备。
static boolean isAudio(InputDevice device) {String name = device.getName();return !TextUtils.isEmpty(name) && (name.contains("Audio") || name.contains("audio") || name.toLowerCase().contains("audio") || name.toUpperCase().contains("AUDIO"));
}private final static List<String> mInputDeviceNameBackList = Arrays.asList(// Philips Settings 已经过滤的不提示的设备列表(USB Dongle 设备列表)"Wireless Gamepad F710","Logitech Cordless RumblePad 2","Bluetooth Mouse M557","Bluetooth Mouse M336/M337/M535",// Philips Settings 已经过滤的不提示的 Philips 品牌遥控器的设备列表(Philips 品牌遥控器列表)"PHLRC","PHLCB","PHL45C2","PHL44CB","A15BC","P45C2B","PHL44C","Huitong BLE Remote","RCSP"
);

六、综合上述需求,业务逻辑方面的问题算是比较清晰了,代码也比较好完善,此处略过。

这里的关键是分析并处理客户报出的两个类型不正确的问题,即 为何 USB Dongle 的耳机、USB Dongle 的鼠标 为何会提示 USB 键盘已连接、已删除?

这个问题,根据知识面,可以通过这么几个方式来 Debug:

方式编号 方式介绍 特点 限制
方式1 USB Tree Viewer 工具开源,基于 Windows 的输入设备API,列举设备的所有描述信息和设备性质,好用 只有 windows 电脑可以使用,Mac 目前无法兼容,虚拟机兼容差
方式2 InputManagerListener 根据回调可以得知插入的设备的关键信息,含:设备名称、ID、描述、设备性质等 缺失USB的节点信息,设备信息基本够用
方式3 USB Host Mode 可以观测 USB 主机口热插拔的信息,映射为系统设备的节点,类似 fd 的信息 节点信息完整,设备信息模糊,无法形成较为准确的判断。

由上述对分析方式的说明,可以得知:方式1 和方式2 对分析、解决问题有较为直接的帮助。接下来就是技术方案的确定了,如果是 windows 电脑,我建议方式1、方式2都可以尝试。如果是其他型号电脑,我建议按方式2 分析。

下面我按方式2,给出调试代码与设备兼容性日志:

// Debug 日志TAG,为了方便过滤、分析,暂用姓名(正式软件中,需要按编码要求修正)
private static final String TAG = "zhangfan";private InputManager.InputDeviceListener mInputDeviceListener = new InputManager.InputDeviceListener() {@Overridepublic void onInputDeviceAdded(int deviceId) {// 当设备插入时,回调此函数InputDevice device = mInputManager.getInputDevice(deviceId);Log.d(TAG, "onInputDeviceAdded: inputDevice = " + device);if (device == null) {return;}showInputDeviceMessage(device);if (isKeyBoard(device)) {mDeviceMap.put(deviceId, device);showToast("USB键盘已连接");} else if (isMouse(device)) {mDeviceMap.put(deviceId, device);showToast("USB鼠标已连接");}}@Overridepublic void onInputDeviceRemoved(int deviceId) {// 当设备移除时,回调此函数(设备移除时,无法获取到USB设备的信息,所以上面的MAP就起到了作用)InputDevice device = mDeviceMap.remove(deviceId);Log.d(TAG, "onInputDeviceRemoved: inputDevice = " + device);if (device == null) {return;}showInputDeviceMessage(device);if (isKeyBoard(device)) {mDeviceMap.put(deviceId, device);showToast("USB键盘已删除");} else if (isMouse(device)) {mDeviceMap.put(deviceId, device);showToast("USB鼠标已删除");} }@Overridepublic void onInputDeviceChanged(int deviceId) {// 当设备性质发生动态变化时,回调此函数}
}void init(Context context) {this.mContext = context;// 1. 实例化输入管理器,基于 Binder 的调用,通过 ServiceManager 向 SystemServer 进程发起服务别名为“input”的服务查询,略mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);// 2. 向输入管理器中注册设备监听器,Native 层的 InputReader.cpp 识别到设备变化,向 SystemServer 进程发送刷列表的消息,notifyInputDevicesChanged,收到消息后,向Binder客户端回传设备信息。mInputManager.registerInputDeviceListener(mInputDeviceListener);printInputDeviceConstants();
}private void showInputDeviceMessage(@NonNull InputDevice device) {Log.d(TAG, "\n\n");String name = device.getName();int keyboardType = device.getKeyboardType();int sourceMask = device.getSources();int resultMask1 = sourceMask | InputDevice.SOURCE_MOUSE;int resultMask2 = sourceMask | InputDevice.SOURCE_CLASS_POINTER;int resultMask3 = sourceMask | InputDevice.SOURCE_KEYBOARD;int resultMask4 = sourceMask | InputDevice.SOURCE_CLASS_BUTTON;Log.d(TAG, "showInputDeviceMessage: name = " + name);Log.d(TAG, "showInputDeviceMessage: keyboardType = " + keyboardType); Log.d(TAG, "showInputDeviceMessage: sourceMask = " + sourceMask); Log.d(TAG, "showInputDeviceMessage: resultMask1 = " + resultMask1); Log.d(TAG, "showInputDeviceMessage: resultMask2 = " + resultMask2);  Log.d(TAG, "showInputDeviceMessage: resultMask3 = " + resultMask3);  Log.d(TAG, "showInputDeviceMessage: resultMask4 = " + resultMask4);
}private void printInputDeviceConstants() {Log.d(TAG, "\n\n");Log.d(TAG, "printInputDeviceConstants: SOURCE_CLASS_MASK = " + InputDevice.SOURCE_CLASS_MASK);Log.d(TAG, "printInputDeviceConstants: SOURCE_CLASS_NONE = " + InputDevice.SOURCE_CLASS_NONE);Log.d(TAG, "printInputDeviceConstants: SOURCE_CLASS_BUTTON = " + InputDevice.SOURCE_CLASS_BUTTON);Log.d(TAG, "printInputDeviceConstants: SOURCE_CLASS_POINTER = " + InputDevice.SOURCE_CLASS_POINTER);Log.d(TAG, "printInputDeviceConstants: SOURCE_CLASS_TRACKBALL = " + InputDevice.SOURCE_CLASS_TRACKBALL);Log.d(TAG, "printInputDeviceConstants: SOURCE_CLASS_POSITION = " + InputDevice.SOURCE_CLASS_POSITION);Log.d(TAG, "printInputDeviceConstants: SOURCE_CLASS_JOYSTICK = " + InputDevice.SOURCE_CLASS_JOYSTICK);Log.d(TAG, "printInputDeviceConstants: SOURCE_KEYBOARD = " + InputDevice.SOURCE_KEYBOARD);Log.d(TAG, "printInputDeviceConstants: SOURCE_DPAD = " + InputDevice.SOURCE_DPAD);Log.d(TAG, "printInputDeviceConstants: SOURCE_GAMEPAD = " + InputDevice.SOURCE_GAMEPAD);Log.d(TAG, "printInputDeviceConstants: SOURCE_TOUCHSCREEN = " + InputDevice.SOURCE_TOUCHSCREEN);Log.d(TAG, "printInputDeviceConstants: SOURCE_MOUSE = " + InputDevice.SOURCE_MOUSE);Log.d(TAG, "printInputDeviceConstants: SOURCE_STYLUS = " + InputDevice.SOURCE_STYLUS);Log.d(TAG, "printInputDeviceConstants: SOURCE_BLUETOOTH_STYLUS = " + InputDevice.SOURCE_BLUETOOTH_STYLUS);Log.d(TAG, "printInputDeviceConstants: SOURCE_TRACKBALL = " + InputDevice.SOURCE_TRACKBALL);Log.d(TAG, "printInputDeviceConstants: SOURCE_MOUSE_RELATIVE = " + InputDevice.SOURCE_MOUSE_RELATIVE);Log.d(TAG, "printInputDeviceConstants: SOURCE_ROTARY_ENCODER = " + InputDevice.SOURCE_ROTARY_ENCODER);Log.d(TAG, "printInputDeviceConstants: SOURCE_JOYSTICK = " + InputDevice.SOURCE_JOYSTICK);Log.d(TAG, "printInputDeviceConstants: SOURCE_HDMI = " + InputDevice.SOURCE_HDMI);Log.d(TAG, "printInputDeviceConstants: SOURCE_ANY = " + InputDevice.SOURCE_ANY);
}

添加了丰富的日志后,我们插上 USB Audio 类型的外设后,惊人的看到了如下打印,它虽然是 Audio 设备,但系统返回的却是 keyboard 性质的设备,被咱们的代码判断为键盘了:

于是,插入了其他类型的 Audio 设备,也有类似的情况出现。与客户对比机对比,客户无相关提示,咱们提示了 “USB 键盘已连接、已删除”,结合客户测试人员说插入 USB 耳机应该无提示,那么此处的逻辑就比较好处理了。

按照同样的思路,分析了问题2:USB 鼠标被识别为 USB 键盘,提示了 “USB 键盘已连接、已删除”。

借到了同事的 USB 罗技M590鼠标、小米 2.4G USB Dongle 鼠标、有线鼠标分别模拟了热插拔情况,于是,我们发现了另一幕:

上面的是逻辑 M590 型号鼠标的信息,当设备插入后,系统发起了两次 DeviceAdd 的信息,第一次的设备信息包含(keyboard、dpad)两种性质,第二次的设备信息包含(keyboard、dpad、mouse、joystick)四种性质。

从这里,我们可能会有一个问题:为何我插入了 USB Dongle 鼠标,会收到两次系统的消息回调呢?

其实这个和设备的工作原理有关,USB的热插拔特性和线序可以回答为什么有第一次。

另外,USB Dongle鼠标的工作原理是需要和 Dongle 进行链接,当 USB Dongle 插入电视,被电视供电后,鼠标发射信号与 Dongle 进行链接,触发了系统的第二次消息通知,相当于是把鼠标控制的特性添加到系统识别的输入设备列表中。

我们的软件判断为键盘,显示为 “USB 键盘已连接、已删除”,而客户的软件判断为鼠标,显示为 “USB 鼠标已连接、已删除”。

从这里,大概可以判断出,客户的软件显示了是按照最后一次消息通知来判断设备属性的,而这种较为复杂的多功能外设,优先判断了是否是鼠标,如果有鼠标性质,则不会在判断其他性质,否则再判断是否为键盘。

在我们插入了 小米 2.4G USB Dongle 鼠标的那一刻,刚刚的想法就被证实了。

插入此设备后,一共收到 3 次系统通知,第一次的设备信息包含(keyboard、dpad)两种性质,第二次的设备信息包含(keyboard、mouse)两种性质,第三次的设备信息包含(keyboard、dpad、joystick)三种性质。

按照我们之前的想法,这次客户样机显示的应该是“USB 键盘已连接、已删除”,经过验证,果然如此。

再通过一起其他多功能的鼠标验证,基本说明了我们的判断是正确的,与客户确认,他们也理解了相关做法(对接的人不懂技术)。

经过如上分析,问题的修复措施就相对清晰了起来。

简而言之,只需要在收到add设备的消息时,忽略短时间内的前有提示,按最后一次设备信息中的鼠标、键盘来确认性质即可。

至此,问题的分析基本结束,接下来就是构建业务逻辑代码和细节优化的部分,相对简单就省略带过了。

七、工具使用:

推荐也学习一下 USB Tree Viewer 工具,真的好用,在这个上面可以看到复杂外设的多种性质,而且描述的更为细致、全面,从这个工具展示的效果来看,Windows 比 Android 的接口要丰富,且通用性更强。

也推荐了解下 USB 下的 HM、AM 模式,对需要嵌入式设备通信有开发需要的小伙伴,比较有用。

另外,USB 的调试,也有很多其他的方法,涉及到的范围不尽相同,可以看接下来的一篇关于 Linux USB 设备调试的实用命令。

最后,希望大家在 USB 的学习之路上共同进步,知识越累越多,问题越来越少。

Android USB 输入设备相关推荐

  1. Android USB Accessory方案研究

    申明:本文部分内容为网络相关资料整理,并结合本人实际工作总结而成.请引用或者转载注明出处,对于文章内容有疑问请留言. Android Open Accessory Protocol1.0 Androi ...

  2. 基于AOA协议的android USB通信

    摘 要:AOA协议是Google公司推出的用于实现Android设备与外围设备之间USB通信的协议.该协议拓展了Android设备USB接口的功能,为基于Android系统的智能设备应用于数据采集和设 ...

  3. android挂载usb设备,android usb挂载分析---MountService启动

    在android usb挂载分析----vold启动,我们的vold模块已经启动了,通信的机制也已经建立起来了,接下来我们分析一下MountService的启动,也就是我们FrameWork层的启动, ...

  4. Android USB Host与HID通讯

    Android USB Host与HID通讯 (一) Android USB Host与HID通讯 (二) Android USB Host与HID通讯Demo android usb host 读写 ...

  5. Android USB转串口编程

    安卓手机的对外通信接口就只有USB跟音频口,我们可采用其进行与外设进行通信.今天,我们来讲讲安卓手机利用USB接口与外设进行通信.此时,有两种情况. 第一:USB(手机)<--->USB( ...

  6. Android USB 开发详解

    Android USB 开发详解 先附上 Android USB 官方文档 Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB ...

  7. android USB host编程

    测试手机:华为p8 测试系统:android ------------------------------------------- android的native层usbhost供java层andro ...

  8. Android USB Host与HID通讯(二)

    2019独角兽企业重金招聘Python工程师标准>>> 原文出处:http://han21912.lofter.com/post/c3919_51401d 接上一篇:Android ...

  9. Android+usb+spi,Android设备如何使用USB的硬件接口

    你知道Android设备如何使用USB的硬件接口吗?下面将由学习啦小编带大家来解答这个疑问吧,希望对大家有所收获! 如何处理硬件接口问题 最近业界的发展显示,智能手机/便携系统与自动化系统或机械系统之 ...

最新文章

  1. 《简明 PHP 教程》01 关于 PHP
  2. 利用反射自动封装成实体对象
  3. 并发服务器设计思路,参考apache学习UDP和QoS,研究成果
  4. 分页输入框跳转 java_displaytag 分页-添加页码输入框跳转至指定页
  5. asp.net三层架构制作新闻管理_程序员蜕变为架构师必须要知道的「架构理论」...
  6. node中的异步API
  7. 微软云平台 Azure简介 (三)Windows Azure 存储概述
  8. 在MacOS Big Sur中使用Safari 翻译功能的方法
  9. linux tty 软件包,Linux TTY framework(2)_软件架构
  10. VSTO word操作
  11. 明解c语言答案第八章,明解C语言 入门篇 第八章答案
  12. tfidf+embedding
  13. 诺基亚808 PureView
  14. 计算机运行瓶颈,我的电脑瓶颈在哪呢?
  15. 新宝综述拉升大阳包阴沪指收复3600点
  16. 洛谷 P1304 哥德巴赫猜想
  17. line-height属性与font-size属性的关系
  18. 2020春季《形势与政策》各章节测试答案
  19. 求英国帝国理工学院的iFR研究算法代码
  20. 【VBA(八):在VBA中使用公式】【工作表函数+VBA函数+小结】

热门文章

  1. vi 怎么 保存, 退出编辑
  2. Android如何在账户设置中添加App的账户
  3. Spring Boot使用宝兰德BES进行改造和部署
  4. 基于Three.js和MindAR实现的网页端人脸识别功能和仿抖音猫脸特效换脸的各种面罩实现(含源码)
  5. 软件实用小技巧,大大提高学习、办公效率
  6. 新锐房地产销售管理系统(部分流程)技术解析(七) 销售管理_认筹管理
  7. 2.4.U-Boot配置和编译过程详解-U-Boot和系统移植第4部分视频课程笔记
  8. citrix ADC VPX Models整理
  9. 保健用品智慧供应链管理系统:精细化管理供应商与采购环节,打造敏捷型供应链
  10. 远程办公神器-如何在家里远程办公室的电脑,利用cpolar内网穿透