|  | /* | 
|  | * Copyright (C) 2011 The Android Open Source Project | 
|  | * | 
|  | * 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 an | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package com.android.server.usb; | 
|  |  | 
|  | import static com.android.internal.usb.DumpUtils.writeDevice; | 
|  | import static com.android.internal.util.dump.DumpUtils.writeComponentName; | 
|  |  | 
|  | import android.annotation.NonNull; | 
|  | import android.annotation.Nullable; | 
|  | import android.content.ComponentName; | 
|  | import android.content.Context; | 
|  | import android.hardware.usb.UsbConstants; | 
|  | import android.hardware.usb.UsbDevice; | 
|  | import android.os.Bundle; | 
|  | import android.os.ParcelFileDescriptor; | 
|  | import android.service.ServiceProtoEnums; | 
|  | import android.service.usb.UsbConnectionRecordProto; | 
|  | import android.service.usb.UsbHostManagerProto; | 
|  | import android.service.usb.UsbIsHeadsetProto; | 
|  | import android.text.TextUtils; | 
|  | import android.util.Slog; | 
|  |  | 
|  | import com.android.internal.annotations.GuardedBy; | 
|  | import com.android.internal.util.IndentingPrintWriter; | 
|  | import com.android.internal.util.dump.DualDumpOutputStream; | 
|  | import com.android.server.usb.descriptors.UsbDescriptor; | 
|  | import com.android.server.usb.descriptors.UsbDescriptorParser; | 
|  | import com.android.server.usb.descriptors.UsbDeviceDescriptor; | 
|  | import com.android.server.usb.descriptors.report.TextReportCanvas; | 
|  | import com.android.server.usb.descriptors.tree.UsbDescriptorsTree; | 
|  |  | 
|  | import java.text.SimpleDateFormat; | 
|  | import java.util.Date; | 
|  | import java.util.HashMap; | 
|  | import java.util.LinkedList; | 
|  |  | 
|  | /** | 
|  | * UsbHostManager manages USB state in host mode. | 
|  | */ | 
|  | public class UsbHostManager { | 
|  | private static final String TAG = UsbHostManager.class.getSimpleName(); | 
|  | private static final boolean DEBUG = false; | 
|  | private static final int LINUX_FOUNDATION_VID = 0x1d6b; | 
|  |  | 
|  | private final Context mContext; | 
|  |  | 
|  | // USB busses to exclude from USB host support | 
|  | private final String[] mHostBlacklist; | 
|  |  | 
|  | private final UsbAlsaManager mUsbAlsaManager; | 
|  | private final UsbSettingsManager mSettingsManager; | 
|  |  | 
|  | private final Object mLock = new Object(); | 
|  | @GuardedBy("mLock") | 
|  | // contains all connected USB devices | 
|  | private final HashMap<String, UsbDevice> mDevices = new HashMap<>(); | 
|  |  | 
|  | private Object mSettingsLock = new Object(); | 
|  | @GuardedBy("mSettingsLock") | 
|  | private UsbProfileGroupSettingsManager mCurrentSettings; | 
|  |  | 
|  | private Object mHandlerLock = new Object(); | 
|  | @GuardedBy("mHandlerLock") | 
|  | private ComponentName mUsbDeviceConnectionHandler; | 
|  |  | 
|  | /* | 
|  | * Member used for tracking connections & disconnections | 
|  | */ | 
|  | static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS"); | 
|  | private static final int MAX_CONNECT_RECORDS = 32; | 
|  | private int mNumConnects;    // TOTAL # of connect/disconnect | 
|  | private final LinkedList<ConnectionRecord> mConnections = new LinkedList<ConnectionRecord>(); | 
|  | private ConnectionRecord mLastConnect; | 
|  |  | 
|  | /* | 
|  | * ConnectionRecord | 
|  | * Stores connection/disconnection data. | 
|  | */ | 
|  | class ConnectionRecord { | 
|  | long mTimestamp;        // Same time-base as system log. | 
|  | String mDeviceAddress; | 
|  |  | 
|  | static final int CONNECT = ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT; // 0 | 
|  | static final int CONNECT_BADPARSE = | 
|  | ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT_BADPARSE; // 1 | 
|  | static final int CONNECT_BADDEVICE = | 
|  | ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT_BADDEVICE; // 2 | 
|  | static final int DISCONNECT = | 
|  | ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_DISCONNECT; // -1 | 
|  |  | 
|  | final int mMode; | 
|  | final byte[] mDescriptors; | 
|  |  | 
|  | ConnectionRecord(String deviceAddress, int mode, byte[] descriptors) { | 
|  | mTimestamp = System.currentTimeMillis(); | 
|  | mDeviceAddress = deviceAddress; | 
|  | mMode = mode; | 
|  | mDescriptors = descriptors; | 
|  | } | 
|  |  | 
|  | private String formatTime() { | 
|  | return (new StringBuilder(sFormat.format(new Date(mTimestamp)))).toString(); | 
|  | } | 
|  |  | 
|  | void dump(@NonNull DualDumpOutputStream dump, String idName, long id) { | 
|  | long token = dump.start(idName, id); | 
|  |  | 
|  | dump.write("device_address", UsbConnectionRecordProto.DEVICE_ADDRESS, mDeviceAddress); | 
|  | dump.write("mode", UsbConnectionRecordProto.MODE, mMode); | 
|  | dump.write("timestamp", UsbConnectionRecordProto.TIMESTAMP, mTimestamp); | 
|  |  | 
|  | if (mMode != DISCONNECT) { | 
|  | UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors); | 
|  |  | 
|  | UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor(); | 
|  |  | 
|  | dump.write("manufacturer", UsbConnectionRecordProto.MANUFACTURER, | 
|  | deviceDescriptor.getVendorID()); | 
|  | dump.write("product", UsbConnectionRecordProto.PRODUCT, | 
|  | deviceDescriptor.getProductID()); | 
|  | long isHeadSetToken = dump.start("is_headset", UsbConnectionRecordProto.IS_HEADSET); | 
|  | dump.write("in", UsbIsHeadsetProto.IN, parser.isInputHeadset()); | 
|  | dump.write("out", UsbIsHeadsetProto.OUT, parser.isOutputHeadset()); | 
|  | dump.end(isHeadSetToken); | 
|  | } | 
|  |  | 
|  | dump.end(token); | 
|  | } | 
|  |  | 
|  | void dumpShort(IndentingPrintWriter pw) { | 
|  | if (mMode != DISCONNECT) { | 
|  | pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode); | 
|  | UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors); | 
|  |  | 
|  | UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor(); | 
|  |  | 
|  | pw.println("manfacturer:0x" + Integer.toHexString(deviceDescriptor.getVendorID()) | 
|  | + " product:" + Integer.toHexString(deviceDescriptor.getProductID())); | 
|  | pw.println("isHeadset[in: " + parser.isInputHeadset() | 
|  | + " , out: " + parser.isOutputHeadset() + "]"); | 
|  | } else { | 
|  | pw.println(formatTime() + " Disconnect " + mDeviceAddress); | 
|  | } | 
|  | } | 
|  |  | 
|  | void dumpTree(IndentingPrintWriter pw) { | 
|  | if (mMode != DISCONNECT) { | 
|  | pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode); | 
|  | UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors); | 
|  | StringBuilder stringBuilder = new StringBuilder(); | 
|  | UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree(); | 
|  | descriptorTree.parse(parser); | 
|  | descriptorTree.report(new TextReportCanvas(parser, stringBuilder)); | 
|  |  | 
|  | stringBuilder.append("isHeadset[in: " + parser.isInputHeadset() | 
|  | + " , out: " + parser.isOutputHeadset() + "]"); | 
|  | pw.println(stringBuilder.toString()); | 
|  | } else { | 
|  | pw.println(formatTime() + " Disconnect " + mDeviceAddress); | 
|  | } | 
|  | } | 
|  |  | 
|  | void dumpList(IndentingPrintWriter pw) { | 
|  | if (mMode != DISCONNECT) { | 
|  | pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode); | 
|  | UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors); | 
|  | StringBuilder stringBuilder = new StringBuilder(); | 
|  | TextReportCanvas canvas = new TextReportCanvas(parser, stringBuilder); | 
|  | for (UsbDescriptor descriptor : parser.getDescriptors()) { | 
|  | descriptor.report(canvas); | 
|  | } | 
|  | pw.println(stringBuilder.toString()); | 
|  |  | 
|  | pw.println("isHeadset[in: " + parser.isInputHeadset() | 
|  | + " , out: " + parser.isOutputHeadset() + "]"); | 
|  | } else { | 
|  | pw.println(formatTime() + " Disconnect " + mDeviceAddress); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final int kDumpBytesPerLine = 16; | 
|  |  | 
|  | void dumpRaw(IndentingPrintWriter pw) { | 
|  | if (mMode != DISCONNECT) { | 
|  | pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode); | 
|  | int length = mDescriptors.length; | 
|  | pw.println("Raw Descriptors " + length + " bytes"); | 
|  | int dataOffset = 0; | 
|  | for (int line = 0; line < length / kDumpBytesPerLine; line++) { | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | for (int offset = 0; offset < kDumpBytesPerLine; offset++) { | 
|  | sb.append("0x") | 
|  | .append(String.format("0x%02X", mDescriptors[dataOffset++])) | 
|  | .append(" "); | 
|  | } | 
|  | pw.println(sb.toString()); | 
|  | } | 
|  |  | 
|  | // remainder | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | while (dataOffset < length) { | 
|  | sb.append("0x") | 
|  | .append(String.format("0x%02X", mDescriptors[dataOffset++])) | 
|  | .append(" "); | 
|  | } | 
|  | pw.println(sb.toString()); | 
|  | } else { | 
|  | pw.println(formatTime() + " Disconnect " + mDeviceAddress); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * UsbHostManager | 
|  | */ | 
|  | public UsbHostManager(Context context, UsbAlsaManager alsaManager, | 
|  | UsbSettingsManager settingsManager) { | 
|  | mContext = context; | 
|  |  | 
|  | mHostBlacklist = context.getResources().getStringArray( | 
|  | com.android.internal.R.array.config_usbHostBlacklist); | 
|  | mUsbAlsaManager = alsaManager; | 
|  | mSettingsManager = settingsManager; | 
|  | String deviceConnectionHandler = context.getResources().getString( | 
|  | com.android.internal.R.string.config_UsbDeviceConnectionHandling_component); | 
|  | if (!TextUtils.isEmpty(deviceConnectionHandler)) { | 
|  | setUsbDeviceConnectionHandler(ComponentName.unflattenFromString( | 
|  | deviceConnectionHandler)); | 
|  | } | 
|  | } | 
|  |  | 
|  | public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) { | 
|  | synchronized (mSettingsLock) { | 
|  | mCurrentSettings = settings; | 
|  | } | 
|  | } | 
|  |  | 
|  | private UsbProfileGroupSettingsManager getCurrentUserSettings() { | 
|  | synchronized (mSettingsLock) { | 
|  | return mCurrentSettings; | 
|  | } | 
|  | } | 
|  |  | 
|  | public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) { | 
|  | synchronized (mHandlerLock) { | 
|  | mUsbDeviceConnectionHandler = usbDeviceConnectionHandler; | 
|  | } | 
|  | } | 
|  |  | 
|  | private @Nullable ComponentName getUsbDeviceConnectionHandler() { | 
|  | synchronized (mHandlerLock) { | 
|  | return mUsbDeviceConnectionHandler; | 
|  | } | 
|  | } | 
|  |  | 
|  | private boolean isBlackListed(String deviceAddress) { | 
|  | int count = mHostBlacklist.length; | 
|  | for (int i = 0; i < count; i++) { | 
|  | if (deviceAddress.startsWith(mHostBlacklist[i])) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* returns true if the USB device should not be accessible by applications */ | 
|  | private boolean isBlackListed(int clazz, int subClass) { | 
|  | // blacklist hubs | 
|  | if (clazz == UsbConstants.USB_CLASS_HUB) return true; | 
|  |  | 
|  | // blacklist HID boot devices (mouse and keyboard) | 
|  | return clazz == UsbConstants.USB_CLASS_HID | 
|  | && subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT; | 
|  |  | 
|  | } | 
|  |  | 
|  | private void addConnectionRecord(String deviceAddress, int mode, byte[] rawDescriptors) { | 
|  | mNumConnects++; | 
|  | while (mConnections.size() >= MAX_CONNECT_RECORDS) { | 
|  | mConnections.removeFirst(); | 
|  | } | 
|  | ConnectionRecord rec = | 
|  | new ConnectionRecord(deviceAddress, mode, rawDescriptors); | 
|  | mConnections.add(rec); | 
|  | if (mode != ConnectionRecord.DISCONNECT) { | 
|  | mLastConnect = rec; | 
|  | } | 
|  | } | 
|  |  | 
|  | private void logUsbDevice(UsbDescriptorParser descriptorParser) { | 
|  | int vid = 0; | 
|  | int pid = 0; | 
|  | String mfg = "<unknown>"; | 
|  | String product = "<unknown>"; | 
|  | String version = "<unknown>"; | 
|  | String serial = "<unknown>"; | 
|  |  | 
|  | UsbDeviceDescriptor deviceDescriptor = descriptorParser.getDeviceDescriptor(); | 
|  | if (deviceDescriptor != null) { | 
|  | vid = deviceDescriptor.getVendorID(); | 
|  | pid = deviceDescriptor.getProductID(); | 
|  | mfg = deviceDescriptor.getMfgString(descriptorParser); | 
|  | product = deviceDescriptor.getProductString(descriptorParser); | 
|  | version = deviceDescriptor.getDeviceReleaseString(); | 
|  | serial = deviceDescriptor.getSerialString(descriptorParser); | 
|  | } | 
|  |  | 
|  | if (vid == LINUX_FOUNDATION_VID) { | 
|  | return;  // don't care about OS-constructed virtual USB devices. | 
|  | } | 
|  | boolean hasAudio = descriptorParser.hasAudioInterface(); | 
|  | boolean hasHid = descriptorParser.hasHIDInterface(); | 
|  | boolean hasStorage = descriptorParser.hasStorageInterface(); | 
|  |  | 
|  | String attachedString = "USB device attached: "; | 
|  | attachedString += String.format("vidpid %04x:%04x", vid, pid); | 
|  | attachedString += String.format(" mfg/product/ver/serial %s/%s/%s/%s", | 
|  | mfg, product, version, serial); | 
|  | attachedString += String.format(" hasAudio/HID/Storage: %b/%b/%b", | 
|  | hasAudio, hasHid, hasStorage); | 
|  | Slog.d(TAG, attachedString); | 
|  | } | 
|  |  | 
|  | /* Called from JNI in monitorUsbHostBus() to report new USB devices | 
|  | Returns true if successful, i.e. the USB Audio device descriptors are | 
|  | correctly parsed and the unique device is added to the audio device list. | 
|  | */ | 
|  | @SuppressWarnings("unused") | 
|  | private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass, | 
|  | byte[] descriptors) { | 
|  | if (DEBUG) { | 
|  | Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start"); | 
|  | } | 
|  |  | 
|  | if (isBlackListed(deviceAddress)) { | 
|  | if (DEBUG) { | 
|  | Slog.d(TAG, "device address is black listed"); | 
|  | } | 
|  | return false; | 
|  | } | 
|  | UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors); | 
|  | logUsbDevice(parser); | 
|  |  | 
|  | if (isBlackListed(deviceClass, deviceSubclass)) { | 
|  | if (DEBUG) { | 
|  | Slog.d(TAG, "device class is black listed"); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | synchronized (mLock) { | 
|  | if (mDevices.get(deviceAddress) != null) { | 
|  | Slog.w(TAG, "device already on mDevices list: " + deviceAddress); | 
|  | //TODO If this is the same peripheral as is being connected, replace | 
|  | // it with the new connection. | 
|  | return false; | 
|  | } | 
|  |  | 
|  | UsbDevice newDevice = parser.toAndroidUsbDevice(); | 
|  | if (newDevice == null) { | 
|  | Slog.e(TAG, "Couldn't create UsbDevice object."); | 
|  | // Tracking | 
|  | addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE, | 
|  | parser.getRawDescriptors()); | 
|  | } else { | 
|  | mDevices.put(deviceAddress, newDevice); | 
|  | Slog.d(TAG, "Added device " + newDevice); | 
|  |  | 
|  | // It is fine to call this only for the current user as all broadcasts are | 
|  | // sent to all profiles of the user and the dialogs should only show once. | 
|  | ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler(); | 
|  | if (usbDeviceConnectionHandler == null) { | 
|  | getCurrentUserSettings().deviceAttached(newDevice); | 
|  | } else { | 
|  | getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice, | 
|  | usbDeviceConnectionHandler); | 
|  | } | 
|  |  | 
|  | mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser); | 
|  |  | 
|  | // Tracking | 
|  | addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, | 
|  | parser.getRawDescriptors()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (DEBUG) { | 
|  | Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end"); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* Called from JNI in monitorUsbHostBus to report USB device removal */ | 
|  | @SuppressWarnings("unused") | 
|  | private void usbDeviceRemoved(String deviceAddress) { | 
|  | synchronized (mLock) { | 
|  | UsbDevice device = mDevices.remove(deviceAddress); | 
|  | if (device != null) { | 
|  | Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName()); | 
|  | mUsbAlsaManager.usbDeviceRemoved(deviceAddress/*device*/); | 
|  | mSettingsManager.usbDeviceRemoved(device); | 
|  | getCurrentUserSettings().usbDeviceRemoved(device); | 
|  |  | 
|  | // Tracking | 
|  | addConnectionRecord(deviceAddress, ConnectionRecord.DISCONNECT, null); | 
|  | } else { | 
|  | Slog.d(TAG, "Removed device at " + deviceAddress + " was already gone"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | public void systemReady() { | 
|  | synchronized (mLock) { | 
|  | // Create a thread to call into native code to wait for USB host events. | 
|  | // This thread will call us back on usbDeviceAdded and usbDeviceRemoved. | 
|  | Runnable runnable = this::monitorUsbHostBus; | 
|  | new Thread(null, runnable, "UsbService host thread").start(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Returns a list of all currently attached USB devices */ | 
|  | public void getDeviceList(Bundle devices) { | 
|  | synchronized (mLock) { | 
|  | for (String name : mDevices.keySet()) { | 
|  | devices.putParcelable(name, mDevices.get(name)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Opens the specified USB device */ | 
|  | public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings, | 
|  | String packageName, int uid) { | 
|  | synchronized (mLock) { | 
|  | if (isBlackListed(deviceAddress)) { | 
|  | throw new SecurityException("USB device is on a restricted bus"); | 
|  | } | 
|  | UsbDevice device = mDevices.get(deviceAddress); | 
|  | if (device == null) { | 
|  | // if it is not in mDevices, it either does not exist or is blacklisted | 
|  | throw new IllegalArgumentException( | 
|  | "device " + deviceAddress + " does not exist or is restricted"); | 
|  | } | 
|  |  | 
|  | settings.checkPermission(device, packageName, uid); | 
|  | return nativeOpenDevice(deviceAddress); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Dump out various information about the state of USB device connections. | 
|  | */ | 
|  | public void dump(DualDumpOutputStream dump, String idName, long id) { | 
|  | long token = dump.start(idName, id); | 
|  |  | 
|  | synchronized (mHandlerLock) { | 
|  | if (mUsbDeviceConnectionHandler != null) { | 
|  | writeComponentName(dump, "default_usb_host_connection_handler", | 
|  | UsbHostManagerProto.DEFAULT_USB_HOST_CONNECTION_HANDLER, | 
|  | mUsbDeviceConnectionHandler); | 
|  | } | 
|  | } | 
|  | synchronized (mLock) { | 
|  | for (String name : mDevices.keySet()) { | 
|  | writeDevice(dump, "devices", UsbHostManagerProto.DEVICES, mDevices.get(name)); | 
|  | } | 
|  |  | 
|  | dump.write("num_connects", UsbHostManagerProto.NUM_CONNECTS, mNumConnects); | 
|  |  | 
|  | for (ConnectionRecord rec : mConnections) { | 
|  | rec.dump(dump, "connections", UsbHostManagerProto.CONNECTIONS); | 
|  | } | 
|  | } | 
|  |  | 
|  | dump.end(token); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Dump various descriptor data. | 
|  | */ | 
|  | public void dumpDescriptors(IndentingPrintWriter pw, String[] args) { | 
|  | if (mLastConnect != null) { | 
|  | pw.println("Last Connected USB Device:"); | 
|  | if (args.length <= 1 || args[1].equals("-dump-short")) { | 
|  | mLastConnect.dumpShort(pw); | 
|  | } else if (args[1].equals("-dump-tree")) { | 
|  | mLastConnect.dumpTree(pw); | 
|  | } else if (args[1].equals("-dump-list")) { | 
|  | mLastConnect.dumpList(pw); | 
|  | }  else if (args[1].equals("-dump-raw")) { | 
|  | mLastConnect.dumpRaw(pw); | 
|  | } | 
|  | } else { | 
|  | pw.println("No USB Devices have been connected."); | 
|  | } | 
|  | } | 
|  |  | 
|  | private native void monitorUsbHostBus(); | 
|  | private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress); | 
|  | } |