/* * UVCCamera * library and sample to access to UVC web camera on non-rooted Android device * * Copyright (c) 2014-2017 saki t_saki@serenegiant.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * All files in the folder are under this Apache License, Version 2.0. * Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder * may have a different license, see the respective files. */ package com.serenegiant.usb; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.res.Resources.NotFoundException; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbInterface; import android.text.TextUtils; import android.util.Log; public final class DeviceFilter { private static final String TAG = "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; // USB device manufacturer name string (or null for unspecified) public final String mManufacturerName; // USB device product name string (or null for unspecified) public final String mProductName; // USB device serial number string (or null for unspecified) public final String mSerialNumber; // set true if specific device(s) should exclude public final boolean isExclude; public DeviceFilter(final int vid, final int pid, final int clasz, final int subclass, final int protocol, final String manufacturer, final String product, final String serialNum) { this(vid, pid, clasz, subclass, protocol, manufacturer, product, serialNum, false); } public DeviceFilter(final int vid, final int pid, final int clasz, final int subclass, final int protocol, final String manufacturer, final String product, final String serialNum, final boolean isExclude) { mVendorId = vid; mProductId = pid; mClass = clasz; mSubclass = subclass; mProtocol = protocol; mManufacturerName = manufacturer; mProductName = product; mSerialNumber = serialNum; this.isExclude = isExclude; /* Log.i(TAG, String.format("vendorId=0x%04x,productId=0x%04x,class=0x%02x,subclass=0x%02x,protocol=0x%02x", mVendorId, mProductId, mClass, mSubclass, mProtocol)); */ } public DeviceFilter(final UsbDevice device) { this(device, false); } public DeviceFilter(final UsbDevice device, final boolean isExclude) { mVendorId = device.getVendorId(); mProductId = device.getProductId(); mClass = device.getDeviceClass(); mSubclass = device.getDeviceSubclass(); mProtocol = device.getDeviceProtocol(); mManufacturerName = null; // device.getManufacturerName(); mProductName = null; // device.getProductName(); mSerialNumber = null; // device.getSerialNumber(); this.isExclude = isExclude; /* Log.i(TAG, String.format("vendorId=0x%04x,productId=0x%04x,class=0x%02x,subclass=0x%02x,protocol=0x%02x", mVendorId, mProductId, mClass, mSubclass, mProtocol)); */ } /** * 指定したxmlリソースからDeviceFilterリストを生成する * @param context * @param deviceFilterXmlId * @return */ public static List<DeviceFilter> getDeviceFilters(final Context context, final int deviceFilterXmlId) { final XmlPullParser parser = context.getResources().getXml(deviceFilterXmlId); final List<DeviceFilter> deviceFilters = new ArrayList<DeviceFilter>(); try { int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) { final DeviceFilter deviceFilter = readEntryOne(context, parser); if (deviceFilter != null) { deviceFilters.add(deviceFilter); } } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.d(TAG, "XmlPullParserException", e); } catch (final IOException e) { Log.d(TAG, "IOException", e); } return Collections.unmodifiableList(deviceFilters); } /** * read as integer values with default value from xml(w/o exception throws) * resource integer id is also resolved into integer * @param parser * @param namespace * @param name * @param defaultValue * @return */ private static final int getAttributeInteger(final Context context, final XmlPullParser parser, final String namespace, final String name, final int defaultValue) { int result = defaultValue; try { String v = parser.getAttributeValue(namespace, name); if (!TextUtils.isEmpty(v) && v.startsWith("@")) { final String r = v.substring(1); final int resId = context.getResources().getIdentifier(r, null, context.getPackageName()); if (resId > 0) { result = context.getResources().getInteger(resId); } } else { int radix = 10; if (v != null && v.length() > 2 && v.charAt(0) == '0' && (v.charAt(1) == 'x' || v.charAt(1) == 'X')) { // allow hex values starting with 0x or 0X radix = 16; v = v.substring(2); } result = Integer.parseInt(v, radix); } } catch (final NotFoundException e) { result = defaultValue; } catch (final NumberFormatException e) { result = defaultValue; } catch (final NullPointerException e) { result = defaultValue; } return result; } /** * read as boolean values with default value from xml(w/o exception throws) * resource boolean id is also resolved into boolean * if the value is zero, return false, if the value is non-zero integer, return true * @param context * @param parser * @param namespace * @param name * @param defaultValue * @return */ private static final boolean getAttributeBoolean(final Context context, final XmlPullParser parser, final String namespace, final String name, final boolean defaultValue) { boolean result = defaultValue; try { String v = parser.getAttributeValue(namespace, name); if ("TRUE".equalsIgnoreCase(v)) { result = true; } else if ("FALSE".equalsIgnoreCase(v)) { result = false; } else if (!TextUtils.isEmpty(v) && v.startsWith("@")) { final String r = v.substring(1); final int resId = context.getResources().getIdentifier(r, null, context.getPackageName()); if (resId > 0) { result = context.getResources().getBoolean(resId); } } else { int radix = 10; if (v != null && v.length() > 2 && v.charAt(0) == '0' && (v.charAt(1) == 'x' || v.charAt(1) == 'X')) { // allow hex values starting with 0x or 0X radix = 16; v = v.substring(2); } final int val = Integer.parseInt(v, radix); result = val != 0; } } catch (final NotFoundException e) { result = defaultValue; } catch (final NumberFormatException e) { result = defaultValue; } catch (final NullPointerException e) { result = defaultValue; } return result; } /** * read as String attribute with default value from xml(w/o exception throws) * resource string id is also resolved into string * @param parser * @param namespace * @param name * @param defaultValue * @return */ private static final String getAttributeString(final Context context, final XmlPullParser parser, final String namespace, final String name, final String defaultValue) { String result = defaultValue; try { result = parser.getAttributeValue(namespace, name); if (result == null) result = defaultValue; if (!TextUtils.isEmpty(result) && result.startsWith("@")) { final String r = result.substring(1); final int resId = context.getResources().getIdentifier(r, null, context.getPackageName()); if (resId > 0) result = context.getResources().getString(resId); } } catch (final NotFoundException e) { result = defaultValue; } catch (final NumberFormatException e) { result = defaultValue; } catch (final NullPointerException e) { result = defaultValue; } return result; } public static DeviceFilter readEntryOne(final Context context, final XmlPullParser parser) throws XmlPullParserException, IOException { int vendorId = -1; int productId = -1; int deviceClass = -1; int deviceSubclass = -1; int deviceProtocol = -1; boolean exclude = false; String manufacturerName = null; String productName = null; String serialNumber = null; boolean hasValue = false; String tag; int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { tag = parser.getName(); if (!TextUtils.isEmpty(tag) && (tag.equalsIgnoreCase("usb-device"))) { if (eventType == XmlPullParser.START_TAG) { hasValue = true; vendorId = getAttributeInteger(context, parser, null, "vendor-id", -1); if (vendorId == -1) { vendorId = getAttributeInteger(context, parser, null, "vendorId", -1); if (vendorId == -1) vendorId = getAttributeInteger(context, parser, null, "venderId", -1); } productId = getAttributeInteger(context, parser, null, "product-id", -1); if (productId == -1) productId = getAttributeInteger(context, parser, null, "productId", -1); deviceClass = getAttributeInteger(context, parser, null, "class", -1); deviceSubclass = getAttributeInteger(context, parser, null, "subclass", -1); deviceProtocol = getAttributeInteger(context, parser, null, "protocol", -1); manufacturerName = getAttributeString(context, parser, null, "manufacturer-name", null); if (TextUtils.isEmpty(manufacturerName)) manufacturerName = getAttributeString(context, parser, null, "manufacture", null); productName = getAttributeString(context, parser, null, "product-name", null); if (TextUtils.isEmpty(productName)) productName = getAttributeString(context, parser, null, "product", null); serialNumber = getAttributeString(context, parser, null, "serial-number", null); if (TextUtils.isEmpty(serialNumber)) serialNumber = getAttributeString(context, parser, null, "serial", null); exclude = getAttributeBoolean(context, parser, null, "exclude", false); } else if (eventType == XmlPullParser.END_TAG) { if (hasValue) { return new DeviceFilter(vendorId, productId, deviceClass, deviceSubclass, deviceProtocol, manufacturerName, productName, serialNumber, exclude); } } } eventType = parser.next(); } return null; } /* public void write(XmlSerializer serializer) throws IOException { serializer.startTag(null, "usb-device"); if (mVendorId != -1) { serializer .attribute(null, "vendor-id", Integer.toString(mVendorId)); } if (mProductId != -1) { serializer.attribute(null, "product-id", Integer.toString(mProductId)); } if (mClass != -1) { serializer.attribute(null, "class", Integer.toString(mClass)); } if (mSubclass != -1) { serializer.attribute(null, "subclass", Integer.toString(mSubclass)); } if (mProtocol != -1) { serializer.attribute(null, "protocol", Integer.toString(mProtocol)); } if (mManufacturerName != null) { serializer.attribute(null, "manufacturer-name", mManufacturerName); } if (mProductName != null) { serializer.attribute(null, "product-name", mProductName); } if (mSerialNumber != null) { serializer.attribute(null, "serial-number", mSerialNumber); } serializer.attribute(null, "serial-number", Boolean.toString(isExclude)); serializer.endTag(null, "usb-device"); } */ /** * 指定したクラス・サブクラス・プロトコルがこのDeviceFilterとマッチするかどうかを返す * mExcludeフラグは別途#isExcludeか自前でチェックすること * @param clasz * @param subclass * @param protocol * @return */ private boolean matches(final int clasz, final int subclass, final int protocol) { return ((mClass == -1 || clasz == mClass) && (mSubclass == -1 || subclass == mSubclass) && (mProtocol == -1 || protocol == mProtocol)); } /** * 指定したUsbDeviceがこのDeviceFilterにマッチするかどうかを返す * mExcludeフラグは別途#isExcludeか自前でチェックすること * @param device * @return */ public boolean matches(final UsbDevice device) { if (mVendorId != -1 && device.getVendorId() != mVendorId) { return false; } if (mProductId != -1 && device.getProductId() != mProductId) { return false; } /* if (mManufacturerName != null && device.getManufacturerName() == null) return false; if (mProductName != null && device.getProductName() == null) return false; if (mSerialNumber != null && device.getSerialNumber() == null) return false; if (mManufacturerName != null && device.getManufacturerName() != null && !mManufacturerName.equals(device.getManufacturerName())) return false; if (mProductName != null && device.getProductName() != null && !mProductName.equals(device.getProductName())) return false; if (mSerialNumber != null && device.getSerialNumber() != null && !mSerialNumber.equals(device.getSerialNumber())) 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 final int count = device.getInterfaceCount(); for (int i = 0; i < count; i++) { final UsbInterface intf = device.getInterface(i); if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), intf.getInterfaceProtocol())) { return true; } } return false; } /** * このDeviceFilterに一致してかつmExcludeがtrueならtrueを返す * @param device * @return */ public boolean isExclude(final UsbDevice device) { return isExclude && matches(device); } /** * これって要らんかも, equalsでできる気が * @param f * @return */ public boolean matches(final DeviceFilter f) { if (isExclude != f.isExclude) { return false; } if (mVendorId != -1 && f.mVendorId != mVendorId) { return false; } if (mProductId != -1 && f.mProductId != mProductId) { return false; } if (f.mManufacturerName != null && mManufacturerName == null) { return false; } if (f.mProductName != null && mProductName == null) { return false; } if (f.mSerialNumber != null && mSerialNumber == null) { return false; } if (mManufacturerName != null && f.mManufacturerName != null && !mManufacturerName.equals(f.mManufacturerName)) { return false; } if (mProductName != null && f.mProductName != null && !mProductName.equals(f.mProductName)) { return false; } if (mSerialNumber != null && f.mSerialNumber != null && !mSerialNumber.equals(f.mSerialNumber)) { return false; } // check device class/subclass/protocol return matches(f.mClass, f.mSubclass, f.mProtocol); } @Override public boolean equals(final Object obj) { // can't compare if we have wildcard strings if (mVendorId == -1 || mProductId == -1 || mClass == -1 || mSubclass == -1 || mProtocol == -1) { return false; } if (obj instanceof DeviceFilter) { final DeviceFilter filter = (DeviceFilter) obj; if (filter.mVendorId != mVendorId || filter.mProductId != mProductId || filter.mClass != mClass || filter.mSubclass != mSubclass || filter.mProtocol != mProtocol) { return false; } if ((filter.mManufacturerName != null && mManufacturerName == null) || (filter.mManufacturerName == null && mManufacturerName != null) || (filter.mProductName != null && mProductName == null) || (filter.mProductName == null && mProductName != null) || (filter.mSerialNumber != null && mSerialNumber == null) || (filter.mSerialNumber == null && mSerialNumber != null)) { return false; } if ((filter.mManufacturerName != null && mManufacturerName != null && !mManufacturerName .equals(filter.mManufacturerName)) || (filter.mProductName != null && mProductName != null && !mProductName .equals(filter.mProductName)) || (filter.mSerialNumber != null && mSerialNumber != null && !mSerialNumber .equals(filter.mSerialNumber))) { return false; } return (filter.isExclude != isExclude); } if (obj instanceof UsbDevice) { final UsbDevice device = (UsbDevice) obj; if (isExclude || (device.getVendorId() != mVendorId) || (device.getProductId() != mProductId) || (device.getDeviceClass() != mClass) || (device.getDeviceSubclass() != mSubclass) || (device.getDeviceProtocol() != mProtocol) ) { return false; } /* if ((mManufacturerName != null && device.getManufacturerName() == null) || (mManufacturerName == null && device .getManufacturerName() != null) || (mProductName != null && device.getProductName() == null) || (mProductName == null && device.getProductName() != null) || (mSerialNumber != null && device.getSerialNumber() == null) || (mSerialNumber == null && device.getSerialNumber() != null)) { return (false); } */ /* if ((device.getManufacturerName() != null && !mManufacturerName .equals(device.getManufacturerName())) || (device.getProductName() != null && !mProductName .equals(device.getProductName())) || (device.getSerialNumber() != null && !mSerialNumber .equals(device.getSerialNumber()))) { return (false); } */ return true; } return false; } @Override public int hashCode() { return (((mVendorId << 16) | mProductId) ^ ((mClass << 16) | (mSubclass << 8) | mProtocol)); } @Override public String toString() { return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + ",mClass=" + mClass + ",mSubclass=" + mSubclass + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + ",isExclude=" + isExclude + "]"; } }