You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1411 lines
46 KiB
1411 lines
46 KiB
/*
|
|
* 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 android.annotation.SuppressLint;
|
|
import android.annotation.TargetApi;
|
|
import android.app.PendingIntent;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.hardware.usb.UsbDevice;
|
|
import android.hardware.usb.UsbDeviceConnection;
|
|
import android.hardware.usb.UsbInterface;
|
|
import android.hardware.usb.UsbManager;
|
|
import android.os.Build;
|
|
import android.os.Environment;
|
|
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;
|
|
|
|
import java.io.File;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.lang.ref.WeakReference;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
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;
|
|
|
|
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(ACTION_USB_DEVICE_ATTACHED);
|
|
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");
|
|
// get detected devices
|
|
final HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
|
|
// store those devices info before matching filter xml file
|
|
String fileName = Environment.getExternalStorageDirectory()+ "/USBCamera/failed_devices.txt";
|
|
|
|
File logFile = new File(fileName);
|
|
if(! logFile.exists()) {
|
|
try {
|
|
logFile.createNewFile();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
FileWriter fw = null;
|
|
PrintWriter pw = null;
|
|
final List<UsbDevice> result = new ArrayList<UsbDevice>();
|
|
try {
|
|
fw = new FileWriter(logFile, true);
|
|
pw = new PrintWriter(fw);
|
|
if (deviceList != null) {
|
|
if ((filters == null) || filters.isEmpty()) {
|
|
result.addAll(deviceList.values());
|
|
} else {
|
|
for (final UsbDevice device: deviceList.values() ) {
|
|
// match devices
|
|
for (final DeviceFilter filter: filters) {
|
|
if ((filter != null) && filter.matches(device) || (filter != null && filter.mSubclass == device.getDeviceSubclass())) {
|
|
// when filter matches
|
|
if (!filter.isExclude) {
|
|
result.add(device);
|
|
}
|
|
break;
|
|
} else {
|
|
// collection failed dev's class and subclass
|
|
String devModel = android.os.Build.MODEL;
|
|
String devSystemVersion = android.os.Build.VERSION.RELEASE;
|
|
String devClass = String.valueOf(device.getDeviceClass());
|
|
String subClass = String.valueOf(device.getDeviceSubclass());
|
|
if(pw != null) {
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.append(devModel);
|
|
sb.append("/");
|
|
sb.append(devSystemVersion);
|
|
sb.append(":");
|
|
sb.append("class="+devClass+", subclass="+subClass);
|
|
pw.println(sb.toString());
|
|
pw.flush();
|
|
fw.flush();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
if (pw != null) {
|
|
pw.close();
|
|
}
|
|
if (fw != null) {
|
|
try {
|
|
fw.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
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
|
|
*/
|
|
@TargetApi(Build.VERSION_CODES.M)
|
|
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);
|
|
if(connection == null) {
|
|
return null;
|
|
}
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|