/* * 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.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.os.Handler; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import com.serenegiant.utils.BuildCheck; import com.serenegiant.utils.HandlerThreadHandler; public final class USBMonitor { private static final boolean DEBUG = false; // TODO set false on production private static final String TAG = "USBMonitor"; private static final String ACTION_USB_PERMISSION_BASE = "com.serenegiant.USB_PERMISSION."; private final String ACTION_USB_PERMISSION = ACTION_USB_PERMISSION_BASE + hashCode(); public static final String ACTION_USB_DEVICE_ATTACHED = "android.hardware.usb.action.USB_DEVICE_ATTACHED"; /** * openしているUsbControlBlock */ private final ConcurrentHashMap<UsbDevice, UsbControlBlock> mCtrlBlocks = new ConcurrentHashMap<UsbDevice, UsbControlBlock>(); private final SparseArray<WeakReference<UsbDevice>> mHasPermissions = new SparseArray<WeakReference<UsbDevice>>(); private final WeakReference<Context> mWeakContext; private final UsbManager mUsbManager; private final OnDeviceConnectListener mOnDeviceConnectListener; private PendingIntent mPermissionIntent = null; private List<DeviceFilter> mDeviceFilters = new ArrayList<DeviceFilter>(); /** * コールバックをワーカースレッドで呼び出すためのハンドラー */ private final Handler mAsyncHandler; private volatile boolean destroyed; /** * USB機器の状態変更時のコールバックリスナー */ public interface OnDeviceConnectListener { /** * called when device attached * @param device */ public void onAttach(UsbDevice device); /** * called when device dettach(after onDisconnect) * @param device */ public void onDettach(UsbDevice device); /** * called after device opend * @param device * @param ctrlBlock * @param createNew */ public void onConnect(UsbDevice device, UsbControlBlock ctrlBlock, boolean createNew); /** * called when USB device removed or its power off (this callback is called after device closing) * @param device * @param ctrlBlock */ public void onDisconnect(UsbDevice device, UsbControlBlock ctrlBlock); /** * called when canceled or could not get permission from user * @param device */ public void onCancel(UsbDevice device); } public USBMonitor(final Context context, final OnDeviceConnectListener listener) { if (DEBUG) Log.v(TAG, "USBMonitor:Constructor"); if (listener == null) throw new IllegalArgumentException("OnDeviceConnectListener should not null."); mWeakContext = new WeakReference<Context>(context); mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE); mOnDeviceConnectListener = listener; mAsyncHandler = HandlerThreadHandler.createHandler(TAG); destroyed = false; if (DEBUG) Log.v(TAG, "USBMonitor:mUsbManager=" + mUsbManager); } /** * Release all related resources, * never reuse again */ public void destroy() { if (DEBUG) Log.i(TAG, "destroy:"); unregister(); if (!destroyed) { destroyed = true; // モニターしているUSB機器を全てcloseする final Set<UsbDevice> keys = mCtrlBlocks.keySet(); if (keys != null) { UsbControlBlock ctrlBlock; try { for (final UsbDevice key: keys) { ctrlBlock = mCtrlBlocks.remove(key); if (ctrlBlock != null) { ctrlBlock.close(); } } } catch (final Exception e) { Log.e(TAG, "destroy:", e); } } mCtrlBlocks.clear(); try { mAsyncHandler.getLooper().quit(); } catch (final Exception e) { Log.e(TAG, "destroy:", e); } } } /** * register BroadcastReceiver to monitor USB events * @throws IllegalStateException */ public synchronized void register() throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); if (mPermissionIntent == null) { if (DEBUG) Log.i(TAG, "register:"); final Context context = mWeakContext.get(); if (context != null) { mPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), 0); final IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); // ACTION_USB_DEVICE_ATTACHED never comes on some devices so it should not be added here filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); context.registerReceiver(mUsbReceiver, filter); } // start connection check mDeviceCounts = 0; mAsyncHandler.postDelayed(mDeviceCheckRunnable, 1000); } } /** * unregister BroadcastReceiver * @throws IllegalStateException */ public synchronized void unregister() throws IllegalStateException { // 接続チェック用Runnableを削除 mDeviceCounts = 0; if (!destroyed) { mAsyncHandler.removeCallbacks(mDeviceCheckRunnable); } if (mPermissionIntent != null) { // if (DEBUG) Log.i(TAG, "unregister:"); final Context context = mWeakContext.get(); try { if (context != null) { context.unregisterReceiver(mUsbReceiver); } } catch (final Exception e) { Log.w(TAG, e); } mPermissionIntent = null; } } public synchronized boolean isRegistered() { return !destroyed && (mPermissionIntent != null); } /** * set device filter * @param filter * @throws IllegalStateException */ public void setDeviceFilter(final DeviceFilter filter) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.clear(); mDeviceFilters.add(filter); } /** * デバイスフィルターを追加 * @param filter * @throws IllegalStateException */ public void addDeviceFilter(final DeviceFilter filter) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.add(filter); } /** * デバイスフィルターを削除 * @param filter * @throws IllegalStateException */ public void removeDeviceFilter(final DeviceFilter filter) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.remove(filter); } /** * set device filters * @param filters * @throws IllegalStateException */ public void setDeviceFilter(final List<DeviceFilter> filters) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.clear(); mDeviceFilters.addAll(filters); } /** * add device filters * @param filters * @throws IllegalStateException */ public void addDeviceFilter(final List<DeviceFilter> filters) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.addAll(filters); } /** * remove device filters * @param filters */ public void removeDeviceFilter(final List<DeviceFilter> filters) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); mDeviceFilters.removeAll(filters); } /** * return the number of connected USB devices that matched device filter * @return * @throws IllegalStateException */ public int getDeviceCount() throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); return getDeviceList().size(); } /** * return device list, return empty list if no device matched * @return * @throws IllegalStateException */ public List<UsbDevice> getDeviceList() throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); return getDeviceList(mDeviceFilters); } /** * return device list, return empty list if no device matched * @param filters * @return * @throws IllegalStateException */ public List<UsbDevice> getDeviceList(final List<DeviceFilter> filters) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); final HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList(); final List<UsbDevice> result = new ArrayList<UsbDevice>(); if (deviceList != null) { if ((filters == null) || filters.isEmpty()) { result.addAll(deviceList.values()); } else { for (final UsbDevice device: deviceList.values() ) { for (final DeviceFilter filter: filters) { if ((filter != null) && filter.matches(device)) { // when filter matches if (!filter.isExclude) { result.add(device); } break; } } } } } return result; } /** * return device list, return empty list if no device matched * @param filter * @return * @throws IllegalStateException */ public List<UsbDevice> getDeviceList(final DeviceFilter filter) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); final HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList(); final List<UsbDevice> result = new ArrayList<UsbDevice>(); if (deviceList != null) { for (final UsbDevice device: deviceList.values() ) { if ((filter == null) || (filter.matches(device) && !filter.isExclude)) { result.add(device); } } } return result; } /** * get USB device list, without filter * @return * @throws IllegalStateException */ public Iterator<UsbDevice> getDevices() throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); Iterator<UsbDevice> iterator = null; final HashMap<String, UsbDevice> list = mUsbManager.getDeviceList(); if (list != null) iterator = list.values().iterator(); return iterator; } /** * output device list to LogCat */ public final void dumpDevices() { final HashMap<String, UsbDevice> list = mUsbManager.getDeviceList(); if (list != null) { final Set<String> keys = list.keySet(); if (keys != null && keys.size() > 0) { final StringBuilder sb = new StringBuilder(); for (final String key: keys) { final UsbDevice device = list.get(key); final int num_interface = device != null ? device.getInterfaceCount() : 0; sb.setLength(0); for (int i = 0; i < num_interface; i++) { sb.append(String.format(Locale.US, "interface%d:%s", i, device.getInterface(i).toString())); } Log.i(TAG, "key=" + key + ":" + device + ":" + sb.toString()); } } else { Log.i(TAG, "no device"); } } else { Log.i(TAG, "no device"); } } /** * return whether the specific Usb device has permission * @param device * @return true: 指定したUsbDeviceにパーミッションがある * @throws IllegalStateException */ public final boolean hasPermission(final UsbDevice device) throws IllegalStateException { if (destroyed) throw new IllegalStateException("already destroyed"); return updatePermission(device, device != null && mUsbManager.hasPermission(device)); } /** * 内部で保持しているパーミッション状態を更新 * @param device * @param hasPermission * @return hasPermission */ private boolean updatePermission(final UsbDevice device, final boolean hasPermission) { final int deviceKey = getDeviceKey(device, true); synchronized (mHasPermissions) { if (hasPermission) { if (mHasPermissions.get(deviceKey) == null) { mHasPermissions.put(deviceKey, new WeakReference<UsbDevice>(device)); } } else { mHasPermissions.remove(deviceKey); } } return hasPermission; } /** * request permission to access to USB device * @param device * @return true if fail to request permission */ public synchronized boolean requestPermission(final UsbDevice device) { // if (DEBUG) Log.v(TAG, "requestPermission:device=" + device); boolean result = false; if (isRegistered()) { if (device != null) { if (mUsbManager.hasPermission(device)) { // call onConnect if app already has permission processConnect(device); } else { try { // パーミッションがなければ要求する mUsbManager.requestPermission(device, mPermissionIntent); } catch (final Exception e) { // Android5.1.xのGALAXY系でandroid.permission.sec.MDM_APP_MGMTという意味不明の例外生成するみたい Log.w(TAG, e); processCancel(device); result = true; } } } else { processCancel(device); result = true; } } else { processCancel(device); result = true; } return result; } /** * 指定したUsbDeviceをopenする * @param device * @return * @throws SecurityException パーミッションがなければSecurityExceptionを投げる */ public UsbControlBlock openDevice(final UsbDevice device) throws SecurityException { if (hasPermission(device)) { UsbControlBlock result = mCtrlBlocks.get(device); if (result == null) { result = new UsbControlBlock(USBMonitor.this, device); // この中でopenDeviceする mCtrlBlocks.put(device, result); } return result; } else { throw new SecurityException("has no permission"); } } /** * BroadcastReceiver for USB permission */ private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { if (destroyed) return; final String action = intent.getAction(); if (ACTION_USB_PERMISSION.equals(action)) { // when received the result of requesting USB permission synchronized (USBMonitor.this) { final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { if (device != null) { // get permission, call onConnect processConnect(device); } } else { // failed to get permission processCancel(device); } } } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); updatePermission(device, hasPermission(device)); processAttach(device); } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) { // when device removed final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { UsbControlBlock ctrlBlock = mCtrlBlocks.remove(device); if (ctrlBlock != null) { // cleanup ctrlBlock.close(); } mDeviceCounts = 0; processDettach(device); } } } }; /** number of connected & detected devices */ private volatile int mDeviceCounts = 0; /** * periodically check connected devices and if it changed, call onAttach */ private final Runnable mDeviceCheckRunnable = new Runnable() { @Override public void run() { if (destroyed) return; final List<UsbDevice> devices = getDeviceList(); final int n = devices.size(); final int hasPermissionCounts; final int m; synchronized (mHasPermissions) { hasPermissionCounts = mHasPermissions.size(); mHasPermissions.clear(); for (final UsbDevice device: devices) { hasPermission(device); } m = mHasPermissions.size(); } if ((n > mDeviceCounts) || (m > hasPermissionCounts)) { mDeviceCounts = n; if (mOnDeviceConnectListener != null) { for (int i = 0; i < n; i++) { final UsbDevice device = devices.get(i); mAsyncHandler.post(new Runnable() { @Override public void run() { mOnDeviceConnectListener.onAttach(device); } }); } } } mAsyncHandler.postDelayed(this, 2000); // confirm every 2 seconds } }; /** * open specific USB device * @param device */ private final void processConnect(final UsbDevice device) { if (destroyed) return; updatePermission(device, true); mAsyncHandler.post(new Runnable() { @Override public void run() { if (DEBUG) Log.v(TAG, "processConnect:device=" + device); UsbControlBlock ctrlBlock; final boolean createNew; ctrlBlock = mCtrlBlocks.get(device); if (ctrlBlock == null) { ctrlBlock = new UsbControlBlock(USBMonitor.this, device); mCtrlBlocks.put(device, ctrlBlock); createNew = true; } else { createNew = false; } if (mOnDeviceConnectListener != null) { mOnDeviceConnectListener.onConnect(device, ctrlBlock, createNew); } } }); } private final void processCancel(final UsbDevice device) { if (destroyed) return; if (DEBUG) Log.v(TAG, "processCancel:"); updatePermission(device, false); if (mOnDeviceConnectListener != null) { mAsyncHandler.post(new Runnable() { @Override public void run() { mOnDeviceConnectListener.onCancel(device); } }); } } private final void processAttach(final UsbDevice device) { if (destroyed) return; if (DEBUG) Log.v(TAG, "processAttach:"); if (mOnDeviceConnectListener != null) { mAsyncHandler.post(new Runnable() { @Override public void run() { mOnDeviceConnectListener.onAttach(device); } }); } } private final void processDettach(final UsbDevice device) { if (destroyed) return; if (DEBUG) Log.v(TAG, "processDettach:"); if (mOnDeviceConnectListener != null) { mAsyncHandler.post(new Runnable() { @Override public void run() { mOnDeviceConnectListener.onDettach(device); } }); } } /** * USB機器毎の設定保存用にデバイスキー名を生成する。 * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 * 同種の製品だと同じキー名になるので注意 * @param device nullなら空文字列を返す * @return */ public static final String getDeviceKeyName(final UsbDevice device) { return getDeviceKeyName(device, null, false); } /** * USB機器毎の設定保存用にデバイスキー名を生成する。 * useNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 * @param device * @param useNewAPI * @return */ public static final String getDeviceKeyName(final UsbDevice device, final boolean useNewAPI) { return getDeviceKeyName(device, null, useNewAPI); } /** * USB機器毎の設定保存用にデバイスキー名を生成する。この機器名をHashMapのキーにする * UsbDeviceがopenしている時のみ有効 * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 * serialがnullや空文字でなければserialを含めたデバイスキー名を生成する * useNewAPI=trueでAPIレベルを満たしていればマニュファクチャ名, バージョン, コンフィギュレーションカウントも使う * @param device nullなら空文字列を返す * @param serial UsbDeviceConnection#getSerialで取得したシリアル番号を渡す, nullでuseNewAPI=trueでAPI>=21なら内部で取得 * @param useNewAPI API>=21またはAPI>=23のみで使用可能なメソッドも使用する(ただし機器によってはnullが返ってくるので有効かどうかは機器による) * @return */ @SuppressLint("NewApi") public static final String getDeviceKeyName(final UsbDevice device, final String serial, final boolean useNewAPI) { if (device == null) return ""; final StringBuilder sb = new StringBuilder(); sb.append(device.getVendorId()); sb.append("#"); // API >= 12 sb.append(device.getProductId()); sb.append("#"); // API >= 12 sb.append(device.getDeviceClass()); sb.append("#"); // API >= 12 sb.append(device.getDeviceSubclass()); sb.append("#"); // API >= 12 sb.append(device.getDeviceProtocol()); // API >= 12 if (!TextUtils.isEmpty(serial)) { sb.append("#"); sb.append(serial); } if (useNewAPI && BuildCheck.isAndroid5()) { sb.append("#"); if (TextUtils.isEmpty(serial)) { sb.append(device.getSerialNumber()); sb.append("#"); // API >= 21 } sb.append(device.getManufacturerName()); sb.append("#"); // API >= 21 sb.append(device.getConfigurationCount()); sb.append("#"); // API >= 21 if (BuildCheck.isMarshmallow()) { sb.append(device.getVersion()); sb.append("#"); // API >= 23 } } // if (DEBUG) Log.v(TAG, "getDeviceKeyName:" + sb.toString()); return sb.toString(); } /** * デバイスキーを整数として取得 * getDeviceKeyNameで得られる文字列のhasCodeを取得 * ベンダーID, プロダクトID, デバイスクラス, デバイスサブクラス, デバイスプロトコルから生成 * 同種の製品だと同じデバイスキーになるので注意 * @param device nullなら0を返す * @return */ public static final int getDeviceKey(final UsbDevice device) { return device != null ? getDeviceKeyName(device, null, false).hashCode() : 0; } /** * デバイスキーを整数として取得 * getDeviceKeyNameで得られる文字列のhasCodeを取得 * useNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 * @param device * @param useNewAPI * @return */ public static final int getDeviceKey(final UsbDevice device, final boolean useNewAPI) { return device != null ? getDeviceKeyName(device, null, useNewAPI).hashCode() : 0; } /** * デバイスキーを整数として取得 * getDeviceKeyNameで得られる文字列のhasCodeを取得 * serialがnullでuseNewAPI=falseで同種の製品だと同じデバイスキーになるので注意 * @param device nullなら0を返す * @param serial UsbDeviceConnection#getSerialで取得したシリアル番号を渡す, nullでuseNewAPI=trueでAPI>=21なら内部で取得 * @param useNewAPI API>=21またはAPI>=23のみで使用可能なメソッドも使用する(ただし機器によってはnullが返ってくるので有効かどうかは機器による) * @return */ public static final int getDeviceKey(final UsbDevice device, final String serial, final boolean useNewAPI) { return device != null ? getDeviceKeyName(device, serial, useNewAPI).hashCode() : 0; } public static class UsbDeviceInfo { public String usb_version; public String manufacturer; public String product; public String version; public String serial; private void clear() { usb_version = manufacturer = product = version = serial = null; } @Override public String toString() { return String.format("UsbDevice:usb_version=%s,manufacturer=%s,product=%s,version=%s,serial=%s", usb_version != null ? usb_version : "", manufacturer != null ? manufacturer : "", product != null ? product : "", version != null ? version : "", serial != null ? serial : ""); } } private static final int USB_DIR_OUT = 0; private static final int USB_DIR_IN = 0x80; private static final int USB_TYPE_MASK = (0x03 << 5); private static final int USB_TYPE_STANDARD = (0x00 << 5); private static final int USB_TYPE_CLASS = (0x01 << 5); private static final int USB_TYPE_VENDOR = (0x02 << 5); private static final int USB_TYPE_RESERVED = (0x03 << 5); private static final int USB_RECIP_MASK = 0x1f; private static final int USB_RECIP_DEVICE = 0x00; private static final int USB_RECIP_INTERFACE = 0x01; private static final int USB_RECIP_ENDPOINT = 0x02; private static final int USB_RECIP_OTHER = 0x03; private static final int USB_RECIP_PORT = 0x04; private static final int USB_RECIP_RPIPE = 0x05; private static final int USB_REQ_GET_STATUS = 0x00; private static final int USB_REQ_CLEAR_FEATURE = 0x01; private static final int USB_REQ_SET_FEATURE = 0x03; private static final int USB_REQ_SET_ADDRESS = 0x05; private static final int USB_REQ_GET_DESCRIPTOR = 0x06; private static final int USB_REQ_SET_DESCRIPTOR = 0x07; private static final int USB_REQ_GET_CONFIGURATION = 0x08; private static final int USB_REQ_SET_CONFIGURATION = 0x09; private static final int USB_REQ_GET_INTERFACE = 0x0A; private static final int USB_REQ_SET_INTERFACE = 0x0B; private static final int USB_REQ_SYNCH_FRAME = 0x0C; private static final int USB_REQ_SET_SEL = 0x30; private static final int USB_REQ_SET_ISOCH_DELAY = 0x31; private static final int USB_REQ_SET_ENCRYPTION = 0x0D; private static final int USB_REQ_GET_ENCRYPTION = 0x0E; private static final int USB_REQ_RPIPE_ABORT = 0x0E; private static final int USB_REQ_SET_HANDSHAKE = 0x0F; private static final int USB_REQ_RPIPE_RESET = 0x0F; private static final int USB_REQ_GET_HANDSHAKE = 0x10; private static final int USB_REQ_SET_CONNECTION = 0x11; private static final int USB_REQ_SET_SECURITY_DATA = 0x12; private static final int USB_REQ_GET_SECURITY_DATA = 0x13; private static final int USB_REQ_SET_WUSB_DATA = 0x14; private static final int USB_REQ_LOOPBACK_DATA_WRITE = 0x15; private static final int USB_REQ_LOOPBACK_DATA_READ = 0x16; private static final int USB_REQ_SET_INTERFACE_DS = 0x17; private static final int USB_REQ_STANDARD_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE); // 0x10 private static final int USB_REQ_STANDARD_DEVICE_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE); // 0x90 private static final int USB_REQ_STANDARD_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE); // 0x11 private static final int USB_REQ_STANDARD_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE); // 0x91 private static final int USB_REQ_STANDARD_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT); // 0x12 private static final int USB_REQ_STANDARD_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT); // 0x92 private static final int USB_REQ_CS_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0x20 private static final int USB_REQ_CS_DEVICE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0xa0 private static final int USB_REQ_CS_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0x21 private static final int USB_REQ_CS_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0xa1 private static final int USB_REQ_CS_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0x22 private static final int USB_REQ_CS_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0xa2 private static final int USB_REQ_VENDER_DEVICE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0x40 private static final int USB_REQ_VENDER_DEVICE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE); // 0xc0 private static final int USB_REQ_VENDER_INTERFACE_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0x41 private static final int USB_REQ_VENDER_INTERFACE_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); // 0xc1 private static final int USB_REQ_VENDER_ENDPOINT_SET = (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0x42 private static final int USB_REQ_VENDER_ENDPOINT_GET = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT); // 0xc2 private static final int USB_DT_DEVICE = 0x01; private static final int USB_DT_CONFIG = 0x02; private static final int USB_DT_STRING = 0x03; private static final int USB_DT_INTERFACE = 0x04; private static final int USB_DT_ENDPOINT = 0x05; private static final int USB_DT_DEVICE_QUALIFIER = 0x06; private static final int USB_DT_OTHER_SPEED_CONFIG = 0x07; private static final int USB_DT_INTERFACE_POWER = 0x08; private static final int USB_DT_OTG = 0x09; private static final int USB_DT_DEBUG = 0x0a; private static final int USB_DT_INTERFACE_ASSOCIATION = 0x0b; private static final int USB_DT_SECURITY = 0x0c; private static final int USB_DT_KEY = 0x0d; private static final int USB_DT_ENCRYPTION_TYPE = 0x0e; private static final int USB_DT_BOS = 0x0f; private static final int USB_DT_DEVICE_CAPABILITY = 0x10; private static final int USB_DT_WIRELESS_ENDPOINT_COMP = 0x11; private static final int USB_DT_WIRE_ADAPTER = 0x21; private static final int USB_DT_RPIPE = 0x22; private static final int USB_DT_CS_RADIO_CONTROL = 0x23; private static final int USB_DT_PIPE_USAGE = 0x24; private static final int USB_DT_SS_ENDPOINT_COMP = 0x30; private static final int USB_DT_CS_DEVICE = (USB_TYPE_CLASS | USB_DT_DEVICE); private static final int USB_DT_CS_CONFIG = (USB_TYPE_CLASS | USB_DT_CONFIG); private static final int USB_DT_CS_STRING = (USB_TYPE_CLASS | USB_DT_STRING); private static final int USB_DT_CS_INTERFACE = (USB_TYPE_CLASS | USB_DT_INTERFACE); private static final int USB_DT_CS_ENDPOINT = (USB_TYPE_CLASS | USB_DT_ENDPOINT); private static final int USB_DT_DEVICE_SIZE = 18; /** * 指定したIDのStringディスクリプタから文字列を取得する。取得できなければnull * @param connection * @param id * @param languageCount * @param languages * @return */ private static String getString(final UsbDeviceConnection connection, final int id, final int languageCount, final byte[] languages) { final byte[] work = new byte[256]; String result = null; for (int i = 1; i <= languageCount; i++) { int ret = connection.controlTransfer( USB_REQ_STANDARD_DEVICE_GET, // USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | id, languages[i], work, 256, 0); if ((ret > 2) && (work[0] == ret) && (work[1] == USB_DT_STRING)) { // skip first two bytes(bLength & bDescriptorType), and copy the rest to the string try { result = new String(work, 2, ret - 2, "UTF-16LE"); if (!"Љ".equals(result)) { // 変なゴミが返ってくる時がある break; } else { result = null; } } catch (final UnsupportedEncodingException e) { // ignore } } } return result; } /** * ベンダー名・製品名・バージョン・シリアルを取得する * @param device * @return */ public UsbDeviceInfo getDeviceInfo(final UsbDevice device) { return updateDeviceInfo(mUsbManager, device, null); } /** * ベンダー名・製品名・バージョン・シリアルを取得する * #updateDeviceInfo(final UsbManager, final UsbDevice, final UsbDeviceInfo)のヘルパーメソッド * @param context * @param device * @return */ public static UsbDeviceInfo getDeviceInfo(final Context context, final UsbDevice device) { return updateDeviceInfo((UsbManager)context.getSystemService(Context.USB_SERVICE), device, new UsbDeviceInfo()); } /** * ベンダー名・製品名・バージョン・シリアルを取得する * @param manager * @param device * @param _info * @return */ public static UsbDeviceInfo updateDeviceInfo(final UsbManager manager, final UsbDevice device, final UsbDeviceInfo _info) { final UsbDeviceInfo info = _info != null ? _info : new UsbDeviceInfo(); info.clear(); if (device != null) { if (BuildCheck.isLollipop()) { info.manufacturer = device.getManufacturerName(); info.product = device.getProductName(); info.serial = device.getSerialNumber(); } if (BuildCheck.isMarshmallow()) { info.usb_version = device.getVersion(); } if ((manager != null) && manager.hasPermission(device)) { final UsbDeviceConnection connection = manager.openDevice(device); final byte[] desc = connection.getRawDescriptors(); if (TextUtils.isEmpty(info.usb_version)) { info.usb_version = String.format("%x.%02x", ((int)desc[3] & 0xff), ((int)desc[2] & 0xff)); } if (TextUtils.isEmpty(info.version)) { info.version = String.format("%x.%02x", ((int)desc[13] & 0xff), ((int)desc[12] & 0xff)); } if (TextUtils.isEmpty(info.serial)) { info.serial = connection.getSerial(); } final byte[] languages = new byte[256]; int languageCount = 0; // controlTransfer(int requestType, int request, int value, int index, byte[] buffer, int length, int timeout) try { int result = connection.controlTransfer( USB_REQ_STANDARD_DEVICE_GET, // USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0, 0, languages, 256, 0); if (result > 0) { languageCount = (result - 2) / 2; } if (languageCount > 0) { if (TextUtils.isEmpty(info.manufacturer)) { info.manufacturer = getString(connection, desc[14], languageCount, languages); } if (TextUtils.isEmpty(info.product)) { info.product = getString(connection, desc[15], languageCount, languages); } if (TextUtils.isEmpty(info.serial)) { info.serial = getString(connection, desc[16], languageCount, languages); } } } finally { connection.close(); } } if (TextUtils.isEmpty(info.manufacturer)) { info.manufacturer = USBVendorId.vendorName(device.getVendorId()); } if (TextUtils.isEmpty(info.manufacturer)) { info.manufacturer = String.format("%04x", device.getVendorId()); } if (TextUtils.isEmpty(info.product)) { info.product = String.format("%04x", device.getProductId()); } } return info; } /** * control class * never reuse the instance when it closed */ public static final class UsbControlBlock implements Cloneable { private final WeakReference<USBMonitor> mWeakMonitor; private final WeakReference<UsbDevice> mWeakDevice; protected UsbDeviceConnection mConnection; protected final UsbDeviceInfo mInfo; private final int mBusNum; private final int mDevNum; private final SparseArray<SparseArray<UsbInterface>> mInterfaces = new SparseArray<SparseArray<UsbInterface>>(); /** * this class needs permission to access USB device before constructing * @param monitor * @param device */ private UsbControlBlock(final USBMonitor monitor, final UsbDevice device) { if (DEBUG) Log.i(TAG, "UsbControlBlock:constructor"); mWeakMonitor = new WeakReference<USBMonitor>(monitor); mWeakDevice = new WeakReference<UsbDevice>(device); mConnection = monitor.mUsbManager.openDevice(device); mInfo = updateDeviceInfo(monitor.mUsbManager, device, null); final String name = device.getDeviceName(); final String[] v = !TextUtils.isEmpty(name) ? name.split("/") : null; int busnum = 0; int devnum = 0; if (v != null) { busnum = Integer.parseInt(v[v.length-2]); devnum = Integer.parseInt(v[v.length-1]); } mBusNum = busnum; mDevNum = devnum; // if (DEBUG) { if (mConnection != null) { final int desc = mConnection.getFileDescriptor(); final byte[] rawDesc = mConnection.getRawDescriptors(); Log.i(TAG, String.format(Locale.US, "name=%s,desc=%d,busnum=%d,devnum=%d,rawDesc=", name, desc, busnum, devnum) + rawDesc); } else { Log.e(TAG, "could not connect to device " + name); } // } } /** * copy constructor * @param src * @throws IllegalStateException */ private UsbControlBlock(final UsbControlBlock src) throws IllegalStateException { final USBMonitor monitor = src.getUSBMonitor(); final UsbDevice device = src.getDevice(); if (device == null) { throw new IllegalStateException("device may already be removed"); } mConnection = monitor.mUsbManager.openDevice(device); if (mConnection == null) { throw new IllegalStateException("device may already be removed or have no permission"); } mInfo = updateDeviceInfo(monitor.mUsbManager, device, null); mWeakMonitor = new WeakReference<USBMonitor>(monitor); mWeakDevice = new WeakReference<UsbDevice>(device); mBusNum = src.mBusNum; mDevNum = src.mDevNum; // FIXME USBMonitor.mCtrlBlocksに追加する(今はHashMapなので追加すると置き換わってしまうのでだめ, ListかHashMapにListをぶら下げる?) } /** * duplicate by clone * need permission * USBMonitor never handle cloned UsbControlBlock, you should release it after using it. * @return * @throws CloneNotSupportedException */ @Override public UsbControlBlock clone() throws CloneNotSupportedException { final UsbControlBlock ctrlblock; try { ctrlblock = new UsbControlBlock(this); } catch (final IllegalStateException e) { throw new CloneNotSupportedException(e.getMessage()); } return ctrlblock; } public USBMonitor getUSBMonitor() { return mWeakMonitor.get(); } public final UsbDevice getDevice() { return mWeakDevice.get(); } /** * get device name * @return */ public String getDeviceName() { final UsbDevice device = mWeakDevice.get(); return device != null ? device.getDeviceName() : ""; } /** * get device id * @return */ public int getDeviceId() { final UsbDevice device = mWeakDevice.get(); return device != null ? device.getDeviceId() : 0; } /** * get device key string * @return same value if the devices has same vendor id, product id, device class, device subclass and device protocol */ public String getDeviceKeyName() { return USBMonitor.getDeviceKeyName(mWeakDevice.get()); } /** * get device key string * @param useNewAPI if true, try to use serial number * @return * @throws IllegalStateException */ public String getDeviceKeyName(final boolean useNewAPI) throws IllegalStateException { if (useNewAPI) checkConnection(); return USBMonitor.getDeviceKeyName(mWeakDevice.get(), mInfo.serial, useNewAPI); } /** * get device key * @return * @throws IllegalStateException */ public int getDeviceKey() throws IllegalStateException { checkConnection(); return USBMonitor.getDeviceKey(mWeakDevice.get()); } /** * get device key * @param useNewAPI if true, try to use serial number * @return * @throws IllegalStateException */ public int getDeviceKey(final boolean useNewAPI) throws IllegalStateException { if (useNewAPI) checkConnection(); return USBMonitor.getDeviceKey(mWeakDevice.get(), mInfo.serial, useNewAPI); } /** * get device key string * if device has serial number, use it * @return */ public String getDeviceKeyNameWithSerial() { return USBMonitor.getDeviceKeyName(mWeakDevice.get(), mInfo.serial, false); } /** * get device key * if device has serial number, use it * @return */ public int getDeviceKeyWithSerial() { return getDeviceKeyNameWithSerial().hashCode(); } /** * get UsbDeviceConnection * @return */ public synchronized UsbDeviceConnection getConnection() { return mConnection; } /** * get file descriptor to access USB device * @return * @throws IllegalStateException */ public synchronized int getFileDescriptor() throws IllegalStateException { checkConnection(); return mConnection.getFileDescriptor(); } /** * get raw descriptor for the USB device * @return * @throws IllegalStateException */ public synchronized byte[] getRawDescriptors() throws IllegalStateException { checkConnection(); return mConnection.getRawDescriptors(); } /** * get vendor id * @return */ public int getVenderId() { final UsbDevice device = mWeakDevice.get(); return device != null ? device.getVendorId() : 0; } /** * get product id * @return */ public int getProductId() { final UsbDevice device = mWeakDevice.get(); return device != null ? device.getProductId() : 0; } /** * get version string of USB * @return */ public String getUsbVersion() { return mInfo.usb_version; } /** * get manufacture * @return */ public String getManufacture() { return mInfo.manufacturer; } /** * get product name * @return */ public String getProductName() { return mInfo.product; } /** * get version * @return */ public String getVersion() { return mInfo.version; } /** * get serial number * @return */ public String getSerial() { return mInfo.serial; } public int getBusNum() { return mBusNum; } public int getDevNum() { return mDevNum; } /** * get interface * @param interface_id * @throws IllegalStateException */ public synchronized UsbInterface getInterface(final int interface_id) throws IllegalStateException { return getInterface(interface_id, 0); } /** * get interface * @param interface_id * @param altsetting * @return * @throws IllegalStateException */ public synchronized UsbInterface getInterface(final int interface_id, final int altsetting) throws IllegalStateException { checkConnection(); SparseArray<UsbInterface> intfs = mInterfaces.get(interface_id); if (intfs == null) { intfs = new SparseArray<UsbInterface>(); mInterfaces.put(interface_id, intfs); } UsbInterface intf = intfs.get(altsetting); if (intf == null) { final UsbDevice device = mWeakDevice.get(); final int n = device.getInterfaceCount(); for (int i = 0; i < n; i++) { final UsbInterface temp = device.getInterface(i); if ((temp.getId() == interface_id) && (temp.getAlternateSetting() == altsetting)) { intf = temp; break; } } if (intf != null) { intfs.append(altsetting, intf); } } return intf; } /** * open specific interface * @param intf */ public synchronized void claimInterface(final UsbInterface intf) { claimInterface(intf, true); } public synchronized void claimInterface(final UsbInterface intf, final boolean force) { checkConnection(); mConnection.claimInterface(intf, force); } /** * close interface * @param intf * @throws IllegalStateException */ public synchronized void releaseInterface(final UsbInterface intf) throws IllegalStateException { checkConnection(); final SparseArray<UsbInterface> intfs = mInterfaces.get(intf.getId()); if (intfs != null) { final int index = intfs.indexOfValue(intf); intfs.removeAt(index); if (intfs.size() == 0) { mInterfaces.remove(intf.getId()); } } mConnection.releaseInterface(intf); } /** * Close device * This also close interfaces if they are opened in Java side */ public synchronized void close() { if (DEBUG) Log.i(TAG, "UsbControlBlock#close:"); if (mConnection != null) { final int n = mInterfaces.size(); for (int i = 0; i < n; i++) { final SparseArray<UsbInterface> intfs = mInterfaces.valueAt(i); if (intfs != null) { final int m = intfs.size(); for (int j = 0; j < m; j++) { final UsbInterface intf = intfs.valueAt(j); mConnection.releaseInterface(intf); } intfs.clear(); } } mInterfaces.clear(); mConnection.close(); mConnection = null; final USBMonitor monitor = mWeakMonitor.get(); if (monitor != null) { if (monitor.mOnDeviceConnectListener != null) { monitor.mOnDeviceConnectListener.onDisconnect(mWeakDevice.get(), UsbControlBlock.this); } monitor.mCtrlBlocks.remove(getDevice()); } } } @Override public boolean equals(final Object o) { if (o == null) return false; if (o instanceof UsbControlBlock) { final UsbDevice device = ((UsbControlBlock) o).getDevice(); return device == null ? mWeakDevice.get() == null : device.equals(mWeakDevice.get()); } else if (o instanceof UsbDevice) { return o.equals(mWeakDevice.get()); } return super.equals(o); } // @Override // protected void finalize() throws Throwable { /// close(); // super.finalize(); // } private synchronized void checkConnection() throws IllegalStateException { if (mConnection == null) { throw new IllegalStateException("already closed"); } } } }