使用device_filter.xml资源文件筛选 USB 枚举结果



按照Android USB Host文档中的说明,我设法通过USB_DEVICE_ATTACHED意图检测新的USB设备。若要将通知限制为某些设备,可以指定资源文件:

<activity ...>
...
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
    </intent-filter>
    <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
        android:resource="@xml/device_filter" />
</activity>

device_filter.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="1234" product-id="5678" />
</resources>

问题是,如果在插入 USB 设备后启动服务,则不会收到任何意图。我可以使用getDeviceList来获取设备列表,但希望避免从device_filter.xml文件中重复过滤条件。这可能吗?

过滤

功能在frameworks/base/services/java/com/android/server/usb/UsbSettingsManager.java中实现,但不幸的是,这些是私有的。我提取了它的一部分实现,它可以像这样使用:

private void scanDevices() {
    ArrayList<UsbDevice> devices;
    try {
        devices = UsbDeviceFilter.getMatchingHostDevices(this, R.xml.wifi_devices);
    } catch (Exception e) {
        Log.w(TAG, "Failed to parse devices.xml: " + e.getMessage());
        return;
    }
    for (UsbDevice device : devices) {
        Log.d(TAG, "Matched device " + device);
    }
}

目前只接受主机设备,但添加对辅助设备的支持是微不足道的。

UsbDeviceFilter.xml:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.content.res.XmlResourceParser;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
/**
 * Utility to test whether a USB device is accepted by a device filter. Heavily
 * based on com.android.server.usb.UsbSettingsManager.
 * @author Peter Wu <lekensteyn@gmail.com>
 */
public class UsbDeviceFilter {
    private final List<DeviceFilter> hostDeviceFilters;
    public UsbDeviceFilter(XmlPullParser parser) throws XmlPullParserException,
            IOException {
        hostDeviceFilters = new ArrayList<UsbDeviceFilter.DeviceFilter>();
        int eventType = parser.getEventType();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            String tagName = parser.getName();
            if ("usb-device".equals(tagName)
                    && parser.getEventType() == XmlPullParser.START_TAG) {
                hostDeviceFilters.add(DeviceFilter.read(parser));
            }
            eventType = parser.next();
        }
    }
    public boolean matchesHostDevice(UsbDevice device) {
        for (DeviceFilter filter : hostDeviceFilters) {
            if (filter.matches(device)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Get a list of connected USB Host devices matching the devices filter.
     * @param ctx A non-null application context.
     * @param resourceId The resource ID pointing to a devices filter XML file.
     * @return A list of connected host devices matching the filter. 
     * @throws XmlPullParserException
     * @throws IOException
     */
    public static ArrayList<UsbDevice> getMatchingHostDevices(Context ctx,
            int resourceId) throws XmlPullParserException, IOException {
        UsbManager usbManager = (UsbManager) ctx
                .getSystemService(Context.USB_SERVICE);
        XmlResourceParser parser = ctx.getResources().getXml(resourceId);
        UsbDeviceFilter devFilter;
        try {
            devFilter = new UsbDeviceFilter(parser);
        } finally {
            parser.close();
        }
        ArrayList<UsbDevice> matchedDevices = new ArrayList<UsbDevice>();
        for (UsbDevice device : usbManager.getDeviceList().values()) {
            if (devFilter.matchesHostDevice(device)) {
                matchedDevices.add(device);
            }
        }
        return matchedDevices;
    }
    public static class DeviceFilter {
        // USB Vendor ID (or -1 for unspecified)
        public final int mVendorId;
        // USB Product ID (or -1 for unspecified)
        public final int mProductId;
        // USB device or interface class (or -1 for unspecified)
        public final int mClass;
        // USB device subclass (or -1 for unspecified)
        public final int mSubclass;
        // USB device protocol (or -1 for unspecified)
        public final int mProtocol;
        private DeviceFilter(int vid, int pid, int clasz, int subclass,
                int protocol) {
            mVendorId = vid;
            mProductId = pid;
            mClass = clasz;
            mSubclass = subclass;
            mProtocol = protocol;
        }
        private static DeviceFilter read(XmlPullParser parser) {
            int vendorId = -1;
            int productId = -1;
            int deviceClass = -1;
            int deviceSubclass = -1;
            int deviceProtocol = -1;
            int count = parser.getAttributeCount();
            for (int i = 0; i < count; i++) {
                String name = parser.getAttributeName(i);
                // All attribute values are ints
                int value = Integer.parseInt(parser.getAttributeValue(i));
                if ("vendor-id".equals(name)) {
                    vendorId = value;
                } else if ("product-id".equals(name)) {
                    productId = value;
                } else if ("class".equals(name)) {
                    deviceClass = value;
                } else if ("subclass".equals(name)) {
                    deviceSubclass = value;
                } else if ("protocol".equals(name)) {
                    deviceProtocol = value;
                }
            }
            return new DeviceFilter(vendorId, productId, deviceClass,
                    deviceSubclass, deviceProtocol);
        }
        private boolean matches(int clasz, int subclass, int protocol) {
            return ((mClass == -1 || clasz == mClass)
                    && (mSubclass == -1 || subclass == mSubclass)
                    && (mProtocol == -1 || protocol == mProtocol));
        }
        public boolean matches(UsbDevice device) {
            if (mVendorId != -1 && device.getVendorId() != mVendorId)
                return false;
            if (mProductId != -1 && device.getProductId() != mProductId)
                return false;
            // check device class/subclass/protocol
            if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
                    device.getDeviceProtocol()))
                return true;
            // if device doesn't match, check the interfaces
            int count = device.getInterfaceCount();
            for (int i = 0; i < count; i++) {
                UsbInterface intf = device.getInterface(i);
                if (matches(intf.getInterfaceClass(),
                        intf.getInterfaceSubclass(),
                        intf.getInterfaceProtocol()))
                    return true;
            }
            return false;
        }
    }
}

这个解决方案对我来说非常有效:

<activity ...>
......
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
    </intent-filter>
    <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
        android:resource="@xml/device_filter" />
</activity>

device_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="6790" product-id="29987" />
</resources>

供应商 ID 和产品 ID 适用于通用 CH340 中文 Arduino 克隆现在,权限对话框窗口永远不会弹出。

最新更新