当前位置: 首页 > 工具软件 > USBManager > 使用案例 >

Android UsbManager无法获取HID设备的原因分析及解决方案

祁永嘉
2023-12-01

HID:Human Interface Device。如鼠标、键盘、游戏手柄等;

本文解决方法为系统源码级,非APP解决方案,主要分析流程及原因。


如下正文开始:

关于使用UsbManager获取HID设备的方法,网上有很多文章说明,基本使用如下:

UsbManager manager = (UsbManager) m_context.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();

拿到deviceList之后,再遍历设备列表获取设备信息展示。但是上述方案基本都存在一个问题,就是对于大部分HID设备,比如鼠标,使用上述方法无法获取其相关信息,连接成功后,APP拿到的deviceList为空。

网上的旧文一般有两种方案:

1. 在/system/etc/permissions路径下增加所谓的android.hardware.usb.host.xml权限,本人使用Android R版本亲测无效。

2. 使用InputManager替代UsbManager,可以获取鼠标设备信息,但是只能读取,无法双向交互,不能满足需求。

为解决上述问题,我们再回到最开始的关键点,为何使用UsbManager无法获取鼠标设备信息。

第一步,从USBManager的getDeviceList入手来看下其实现:

371    /**
372     * Returns a HashMap containing all USB devices currently attached.
373     * USB device name is the key for the returned HashMap.
374     * The result will be empty if no devices are attached, or if
375     * USB host mode is inactive or unsupported.
376     *
377     * @return HashMap containing all connected USB devices.
378     */
379    @RequiresFeature(PackageManager.FEATURE_USB_HOST)
380    public HashMap<String,UsbDevice> getDeviceList() {
381        HashMap<String,UsbDevice> result = new HashMap<String,UsbDevice>();
382        if (mService == null) {
383            return result;
384        }
385        Bundle bundle = new Bundle();
386        try {
387            mService.getDeviceList(bundle);
388            for (String name : bundle.keySet()) {
389                result.put(name, (UsbDevice)bundle.get(name));
390            }
391            return result;
392        } catch (RemoteException e) {
393            throw e.rethrowFromSystemServer();
394        }
395    }

可以看出,其数据来源于mService.getDeviceList(bundle)方法,mService为IUsbManager类型,也就是一个aidl接口。要知道其真正做了什么,还需要看调用的具体实现类。

第二步,顺着IUsbManager这个线索继续往下追查,很容易就找到了UsbService这里。具体路径在:/frameworks/base/services/usb/java/com/android/server/usb/UsbService.java

我们继续观察其关键方法getDeviceList,看具体实现

225    /* Returns a list of all currently attached USB devices (host mdoe) */
226    @Override
227    public void getDeviceList(Bundle devices) {
228        if (mHostManager != null) {
229            mHostManager.getDeviceList(devices);
230        }
231    }

于是,进一步追踪mHostManager这个对象

private UsbHostManager mHostManager;

第三步,我们再来看UsbHostManager这个类中的getDeviceList方法。

436    /* Returns a list of all currently attached USB devices */
437    public void getDeviceList(Bundle devices) {
438        synchronized (mLock) {
439            for (String name : mDevices.keySet()) {
440                devices.putParcelable(name, mDevices.get(name));
441            }
442        }
443    }

可见其数据来自于mDevices这个对象,这是一个HashMap存储类型,存储了所有当前连接的USB设备信息。

    // contains all connected USB devices
70    private final HashMap<String, UsbDevice> mDevices = new HashMap<>();

跟踪这个对象的赋值点,我们最终来到了usbDeviceAdded这个关键方法中。

338    /* Called from JNI in monitorUsbHostBus() to report new USB devices
339       Returns true if successful, i.e. the USB Audio device descriptors are
340       correctly parsed and the unique device is added to the audio device list.
341     */
342    @SuppressWarnings("unused")
343    private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
344            byte[] descriptors) {
345        if (DEBUG) {
346            Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
347        }
348
349        if (isBlackListed(deviceAddress)) {
350            if (DEBUG) {
351                Slog.d(TAG, "device address is black listed");
352            }
353            return false;
354        }
355        UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
356        logUsbDevice(parser);
357
358        if (isBlackListed(deviceClass, deviceSubclass)) {
359            if (DEBUG) {
360                Slog.d(TAG, "device class is black listed");
361            }
362            return false;
363        }
364
365        synchronized (mLock) {
366            if (mDevices.get(deviceAddress) != null) {
367                Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
368                //TODO If this is the same peripheral as is being connected, replace
369                // it with the new connection.
370                return false;
371            }
372
373            UsbDevice newDevice = parser.toAndroidUsbDevice();
374            if (newDevice == null) {
375                Slog.e(TAG, "Couldn't create UsbDevice object.");
376                // Tracking
377                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE,
378                        parser.getRawDescriptors());
379            } else {
380                mDevices.put(deviceAddress, newDevice);
381                Slog.d(TAG, "Added device " + newDevice);
382
383                // It is fine to call this only for the current user as all broadcasts are
384                // sent to all profiles of the user and the dialogs should only show once.
385                ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
386                if (usbDeviceConnectionHandler == null) {
387                    getCurrentUserSettings().deviceAttached(newDevice);
388                } else {
389                    getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
390                            usbDeviceConnectionHandler);
391                }
392
393                mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
394
395                // Tracking
396                addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
397                        parser.getRawDescriptors());
398            }
399        }
400
401        if (DEBUG) {
402            Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
403        }
404
405        return true;
406    }

这个方法是本文的重点所在,其中有几个关键的注释和判断条件。

1.先看这个注释“Called from JNI in monitorUsbHostBus() to report new USB devices Returns true if successful”,该方法为usb设备插入时触发,且直接通过JNI方法调用,并不暴露给Android上层应用。

2.在349/358行分别有两个对应的黑名单判断方法对部分设备做了拦截:isBlackListed

所以这里很大可能是由于鼠标设备在接入时,被此处拦截,所以Android上层获取不到。

带着这个猜想我们来做验证。

首先:打开DEBUG开关,这里默认值为false,全编版本。刷机测试,接入鼠标设备,查看log信息,确实走到了上面第二个isBlackListed方法的判断里。我们的猜测是正确的。

358        if (isBlackListed(deviceClass, deviceSubclass)) {
359            if (DEBUG) {
360                Slog.d(TAG, "device class is black listed");
361            }
362            return false;
363        }

到这里,其实我们已经大致搞清楚了为什么APP无法通过USBManager类的公开方法getDeviceList获取鼠标设备信息了,原因就是在这块被拦截掉了。 


接下来我们顺着这个方法继续往下看,是否有修改方案。

先看这个黑名单判断方法。这里有两个参数clazz和subClass,通过对其类型匹配进行拦截。

280    /* returns true if the USB device should not be accessible by applications */
281    private boolean isBlackListed(int clazz, int subClass) {
282        // blacklist hubs
283        if (clazz == UsbConstants.USB_CLASS_HUB) return true;
284
285        // blacklist HID boot devices (mouse and keyboard)
286        return clazz == UsbConstants.USB_CLASS_HID
287                && subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT;
288
289    }

其中还有句关键注释“blacklist HID boot devices (mouse and keyboard)”,这里意思已经很明显了,不过值得注意的是,本人拿了一个普通USB口键盘进行测试,发现还是可以识别的

也就是说鼠标插入时,是满足这个判断条件的,所以这里return true,直接被拦截。那么想要获取鼠标设备,只要保证其不在这里被拦截是否就可以?

继续测试,将该方法后面的return条件直接修改为return false,也就是不做拦截。全编译代码,刷机测试,链接鼠标设备后,确实可以通过UsbManager直接获取设备信息。因此,若想修改这个设定,还是要在源码这里进行判断,给自己开特殊通道,问题得以解决。

 类似资料: