1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions an
14  * limitations under the License.
15  */
16 
17 package com.android.server.usb;
18 
19 import static com.android.internal.usb.DumpUtils.writeDevice;
20 import static com.android.internal.util.dump.DumpUtils.writeComponentName;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.hardware.usb.UsbConstants;
28 import android.hardware.usb.UsbDevice;
29 import android.os.Bundle;
30 import android.os.ParcelFileDescriptor;
31 import android.service.ServiceProtoEnums;
32 import android.service.usb.UsbConnectionRecordProto;
33 import android.service.usb.UsbHostManagerProto;
34 import android.service.usb.UsbIsHeadsetProto;
35 import android.text.TextUtils;
36 import android.util.ArrayMap;
37 import android.util.Slog;
38 
39 import com.android.internal.annotations.GuardedBy;
40 import com.android.internal.util.FrameworkStatsLog;
41 import com.android.internal.util.IndentingPrintWriter;
42 import com.android.internal.util.dump.DualDumpOutputStream;
43 import com.android.server.usb.descriptors.UsbDescriptor;
44 import com.android.server.usb.descriptors.UsbDescriptorParser;
45 import com.android.server.usb.descriptors.UsbDeviceDescriptor;
46 import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
47 import com.android.server.usb.descriptors.report.TextReportCanvas;
48 import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
49 
50 import libcore.io.IoUtils;
51 
52 import java.text.SimpleDateFormat;
53 import java.util.ArrayList;
54 import java.util.Date;
55 import java.util.HashMap;
56 import java.util.HashSet;
57 import java.util.LinkedList;
58 import java.util.Random;
59 
60 /**
61  * UsbHostManager manages USB state in host mode.
62  */
63 public class UsbHostManager {
64     private static final String TAG = UsbHostManager.class.getSimpleName();
65     private static final boolean DEBUG = false;
66     private static final int LINUX_FOUNDATION_VID = 0x1d6b;
67 
68     private final Context mContext;
69 
70     // USB busses to exclude from USB host support
71     private final String[] mHostDenyList;
72 
73     private final UsbAlsaManager mUsbAlsaManager;
74     private final UsbPermissionManager mPermissionManager;
75 
76     private final Object mLock = new Object();
77     @GuardedBy("mLock")
78     // contains all connected USB devices
79     private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
80 
81     private Object mSettingsLock = new Object();
82     @GuardedBy("mSettingsLock")
83     private UsbProfileGroupSettingsManager mCurrentSettings;
84 
85     private Object mHandlerLock = new Object();
86     @GuardedBy("mHandlerLock")
87     private ComponentName mUsbDeviceConnectionHandler;
88 
89     /*
90      * Member used for tracking connections & disconnections
91      */
92     static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
93     private static final int MAX_CONNECT_RECORDS = 32;
94     private int mNumConnects;    // TOTAL # of connect/disconnect
95     private final LinkedList<ConnectionRecord> mConnections = new LinkedList<ConnectionRecord>();
96     private ConnectionRecord mLastConnect;
97     private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>();
98 
99     /**
100      * List of connected MIDI devices. Key on deviceAddress.
101      */
102     private final HashMap<String, ArrayList<UsbDirectMidiDevice>>
103             mMidiDevices = new HashMap<String, ArrayList<UsbDirectMidiDevice>>();
104     private final HashSet<String> mMidiUniqueCodes = new HashSet<String>();
105     private static final int MAX_UNIQUE_CODE_GENERATION_ATTEMPTS = 10;
106     private final Random mRandom = new Random();
107     private final boolean mHasMidiFeature;
108 
109     /*
110      * ConnectionRecord
111      * Stores connection/disconnection data.
112      */
113     class ConnectionRecord {
114         long mTimestamp;        // Same time-base as system log.
115         String mDeviceAddress;
116 
117         static final int CONNECT = ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT; // 0
118         static final int CONNECT_BADPARSE =
119                 ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT_BADPARSE; // 1
120         static final int CONNECT_BADDEVICE =
121                 ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_CONNECT_BADDEVICE; // 2
122         static final int DISCONNECT =
123                 ServiceProtoEnums.USB_CONNECTION_RECORD_MODE_DISCONNECT; // -1
124 
125         final int mMode;
126         final byte[] mDescriptors;
127 
ConnectionRecord(String deviceAddress, int mode, byte[] descriptors)128         ConnectionRecord(String deviceAddress, int mode, byte[] descriptors) {
129             mTimestamp = System.currentTimeMillis();
130             mDeviceAddress = deviceAddress;
131             mMode = mode;
132             mDescriptors = descriptors;
133         }
134 
formatTime()135         private String formatTime() {
136             return (new StringBuilder(sFormat.format(new Date(mTimestamp)))).toString();
137         }
138 
dump(@onNull DualDumpOutputStream dump, String idName, long id)139         void dump(@NonNull DualDumpOutputStream dump, String idName, long id) {
140             long token = dump.start(idName, id);
141 
142             dump.write("device_address", UsbConnectionRecordProto.DEVICE_ADDRESS, mDeviceAddress);
143             dump.write("mode", UsbConnectionRecordProto.MODE, mMode);
144             dump.write("timestamp", UsbConnectionRecordProto.TIMESTAMP, mTimestamp);
145 
146             if (mMode != DISCONNECT) {
147                 UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
148 
149                 UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor();
150 
151                 dump.write("manufacturer", UsbConnectionRecordProto.MANUFACTURER,
152                         deviceDescriptor.getVendorID());
153                 dump.write("product", UsbConnectionRecordProto.PRODUCT,
154                         deviceDescriptor.getProductID());
155                 long isHeadSetToken = dump.start("is_headset", UsbConnectionRecordProto.IS_HEADSET);
156                 dump.write("in", UsbIsHeadsetProto.IN, parser.isInputHeadset());
157                 dump.write("out", UsbIsHeadsetProto.OUT, parser.isOutputHeadset());
158                 dump.end(isHeadSetToken);
159             }
160 
161             dump.end(token);
162         }
163 
dumpShort(IndentingPrintWriter pw)164         void dumpShort(IndentingPrintWriter pw) {
165             if (mMode != DISCONNECT) {
166                 pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
167                 UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
168 
169                 UsbDeviceDescriptor deviceDescriptor = parser.getDeviceDescriptor();
170 
171                 pw.println("manfacturer:0x" + Integer.toHexString(deviceDescriptor.getVendorID())
172                         + " product:" + Integer.toHexString(deviceDescriptor.getProductID()));
173                 pw.println("isHeadset[in: " + parser.isInputHeadset()
174                         + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
175             } else {
176                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
177             }
178         }
179 
dumpTree(IndentingPrintWriter pw)180         void dumpTree(IndentingPrintWriter pw) {
181             if (mMode != DISCONNECT) {
182                 pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
183                 UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
184                 StringBuilder stringBuilder = new StringBuilder();
185                 UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
186                 descriptorTree.parse(parser);
187                 descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
188                 stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
189                         + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
190                 pw.println(stringBuilder.toString());
191             } else {
192                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
193             }
194         }
195 
dumpList(IndentingPrintWriter pw)196         void dumpList(IndentingPrintWriter pw) {
197             if (mMode != DISCONNECT) {
198                 pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
199                 UsbDescriptorParser parser = new UsbDescriptorParser(mDeviceAddress, mDescriptors);
200                 StringBuilder stringBuilder = new StringBuilder();
201                 TextReportCanvas canvas = new TextReportCanvas(parser, stringBuilder);
202                 for (UsbDescriptor descriptor : parser.getDescriptors()) {
203                     descriptor.report(canvas);
204                 }
205                 pw.println(stringBuilder.toString());
206                 pw.println("isHeadset[in: " + parser.isInputHeadset()
207                         + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock());
208             } else {
209                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
210             }
211         }
212 
213         private static final int kDumpBytesPerLine = 16;
214 
dumpRaw(IndentingPrintWriter pw)215         void dumpRaw(IndentingPrintWriter pw) {
216             if (mMode != DISCONNECT) {
217                 pw.println(formatTime() + " Connect " + mDeviceAddress + " mode:" + mMode);
218                 int length = mDescriptors.length;
219                 pw.println("Raw Descriptors " + length + " bytes");
220                 int dataOffset = 0;
221                 for (int line = 0; line < length / kDumpBytesPerLine; line++) {
222                     StringBuilder sb = new StringBuilder();
223                     for (int offset = 0; offset < kDumpBytesPerLine; offset++) {
224                         sb.append("0x")
225                             .append(String.format("0x%02X", mDescriptors[dataOffset++]))
226                             .append(" ");
227                     }
228                     pw.println(sb.toString());
229                 }
230 
231                 // remainder
232                 StringBuilder sb = new StringBuilder();
233                 while (dataOffset < length) {
234                     sb.append("0x")
235                         .append(String.format("0x%02X", mDescriptors[dataOffset++]))
236                         .append(" ");
237                 }
238                 pw.println(sb.toString());
239             } else {
240                 pw.println(formatTime() + " Disconnect " + mDeviceAddress);
241             }
242         }
243     }
244 
245     /*
246      * UsbHostManager
247      */
UsbHostManager(Context context, UsbAlsaManager alsaManager, UsbPermissionManager permissionManager)248     public UsbHostManager(Context context, UsbAlsaManager alsaManager,
249             UsbPermissionManager permissionManager) {
250         mContext = context;
251 
252         mHostDenyList = context.getResources().getStringArray(
253                 com.android.internal.R.array.config_usbHostDenylist);
254         mUsbAlsaManager = alsaManager;
255         mPermissionManager = permissionManager;
256         String deviceConnectionHandler = context.getResources().getString(
257                 com.android.internal.R.string.config_UsbDeviceConnectionHandling_component);
258         if (!TextUtils.isEmpty(deviceConnectionHandler)) {
259             setUsbDeviceConnectionHandler(ComponentName.unflattenFromString(
260                     deviceConnectionHandler));
261         }
262         mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
263     }
264 
setCurrentUserSettings(UsbProfileGroupSettingsManager settings)265     public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
266         synchronized (mSettingsLock) {
267             mCurrentSettings = settings;
268         }
269     }
270 
getCurrentUserSettings()271     private UsbProfileGroupSettingsManager getCurrentUserSettings() {
272         synchronized (mSettingsLock) {
273             return mCurrentSettings;
274         }
275     }
276 
setUsbDeviceConnectionHandler(@ullable ComponentName usbDeviceConnectionHandler)277     public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
278         synchronized (mHandlerLock) {
279             mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
280         }
281     }
282 
getUsbDeviceConnectionHandler()283     private @Nullable ComponentName getUsbDeviceConnectionHandler() {
284         synchronized (mHandlerLock) {
285             return mUsbDeviceConnectionHandler;
286         }
287     }
288 
isDenyListed(String deviceAddress)289     private boolean isDenyListed(String deviceAddress) {
290         int count = mHostDenyList.length;
291         for (int i = 0; i < count; i++) {
292             if (deviceAddress.startsWith(mHostDenyList[i])) {
293                 return true;
294             }
295         }
296         return false;
297     }
298 
299     /* returns true if the USB device should not be accessible by applications */
isDenyListed(int clazz, int subClass)300     private boolean isDenyListed(int clazz, int subClass) {
301         // deny hubs
302         if (clazz == UsbConstants.USB_CLASS_HUB) return true;
303 
304         // deny HID boot devices (mouse and keyboard)
305         return clazz == UsbConstants.USB_CLASS_HID
306                 && subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT;
307 
308     }
309 
addConnectionRecord(String deviceAddress, int mode, byte[] rawDescriptors)310     private void addConnectionRecord(String deviceAddress, int mode, byte[] rawDescriptors) {
311         mNumConnects++;
312         while (mConnections.size() >= MAX_CONNECT_RECORDS) {
313             mConnections.removeFirst();
314         }
315         ConnectionRecord rec =
316                 new ConnectionRecord(deviceAddress, mode, rawDescriptors);
317         mConnections.add(rec);
318         if (mode != ConnectionRecord.DISCONNECT) {
319             mLastConnect = rec;
320         }
321         if (mode == ConnectionRecord.CONNECT) {
322             mConnected.put(deviceAddress, rec);
323         } else if (mode == ConnectionRecord.DISCONNECT) {
324             mConnected.remove(deviceAddress);
325         }
326     }
327 
logUsbDevice(UsbDescriptorParser descriptorParser)328     private void logUsbDevice(UsbDescriptorParser descriptorParser) {
329         int vid = 0;
330         int pid = 0;
331         String mfg = "<unknown>";
332         String product = "<unknown>";
333         String version = "<unknown>";
334         String serial = "<unknown>";
335 
336         UsbDeviceDescriptor deviceDescriptor = descriptorParser.getDeviceDescriptor();
337         if (deviceDescriptor != null) {
338             vid = deviceDescriptor.getVendorID();
339             pid = deviceDescriptor.getProductID();
340             mfg = deviceDescriptor.getMfgString(descriptorParser);
341             product = deviceDescriptor.getProductString(descriptorParser);
342             version = deviceDescriptor.getDeviceReleaseString();
343             serial = deviceDescriptor.getSerialString(descriptorParser);
344         }
345 
346         if (vid == LINUX_FOUNDATION_VID) {
347             return;  // don't care about OS-constructed virtual USB devices.
348         }
349         boolean hasAudio = descriptorParser.hasAudioInterface();
350         boolean hasHid = descriptorParser.hasHIDInterface();
351         boolean hasStorage = descriptorParser.hasStorageInterface();
352 
353         String attachedString = "USB device attached: ";
354         attachedString += String.format("vidpid %04x:%04x", vid, pid);
355         attachedString += String.format(" mfg/product/ver/serial %s/%s/%s/%s",
356                                         mfg, product, version, serial);
357         attachedString += String.format(" hasAudio/HID/Storage: %b/%b/%b",
358                                         hasAudio, hasHid, hasStorage);
359         Slog.d(TAG, attachedString);
360     }
361 
362     /* Called from JNI in monitorUsbHostBus() to report new USB devices
363        Returns true if successful, i.e. the USB Audio device descriptors are
364        correctly parsed and the unique device is added to the audio device list.
365      */
366     @SuppressWarnings("unused")
usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass, byte[] descriptors)367     private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
368             byte[] descriptors) {
369         if (DEBUG) {
370             Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
371         }
372 
373         if (isDenyListed(deviceAddress)) {
374             if (DEBUG) {
375                 Slog.d(TAG, "device address is Deny listed");
376             }
377             return false;
378         }
379 
380         if (isDenyListed(deviceClass, deviceSubclass)) {
381             if (DEBUG) {
382                 Slog.d(TAG, "device class is deny listed");
383             }
384             return false;
385         }
386 
387         UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
388         if (deviceClass == UsbConstants.USB_CLASS_PER_INTERFACE
389                 && !checkUsbInterfacesDenyListed(parser)) {
390             return false;
391         }
392 
393         // Potentially can block as it may read data from the USB device.
394         logUsbDevice(parser);
395 
396         synchronized (mLock) {
397             if (mDevices.get(deviceAddress) != null) {
398                 Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
399                 //TODO If this is the same peripheral as is being connected, replace
400                 // it with the new connection.
401                 return false;
402             }
403 
404             UsbDevice.Builder newDeviceBuilder = parser.toAndroidUsbDeviceBuilder();
405             if (newDeviceBuilder == null) {
406                 Slog.e(TAG, "Couldn't create UsbDevice object.");
407                 // Tracking
408                 addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT_BADDEVICE,
409                         parser.getRawDescriptors());
410             } else {
411                 UsbSerialReader serialNumberReader = new UsbSerialReader(mContext,
412                         mPermissionManager, newDeviceBuilder.serialNumber);
413                 UsbDevice newDevice = newDeviceBuilder.build(serialNumberReader);
414                 serialNumberReader.setDevice(newDevice);
415 
416                 mDevices.put(deviceAddress, newDevice);
417                 Slog.d(TAG, "Added device " + newDevice);
418 
419                 // It is fine to call this only for the current user as all broadcasts are
420                 // sent to all profiles of the user and the dialogs should only show once.
421                 ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
422                 if (usbDeviceConnectionHandler == null) {
423                     getCurrentUserSettings().deviceAttached(newDevice);
424                 } else {
425                     getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
426                             usbDeviceConnectionHandler);
427                 }
428 
429                 mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
430 
431                 if (mHasMidiFeature) {
432                     // Use a 3 digit code to associate MIDI devices with one another.
433                     // Each MIDI device already has mId for uniqueness. mId is generated
434                     // sequentially. For clarity, this code is not generated sequentially.
435                     String uniqueUsbDeviceIdentifier = generateNewUsbDeviceIdentifier();
436 
437                     ArrayList<UsbDirectMidiDevice> midiDevices =
438                             new ArrayList<UsbDirectMidiDevice>();
439                     if (parser.containsUniversalMidiDeviceEndpoint()) {
440                         UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
441                                 newDevice, parser, true, uniqueUsbDeviceIdentifier);
442                         if (midiDevice != null) {
443                             midiDevices.add(midiDevice);
444                         } else {
445                             Slog.e(TAG, "Universal Midi Device is null.");
446                         }
447 
448                         // Use UsbDirectMidiDevice only if this supports MIDI 2.0 as well.
449                         // ALSA removes the audio sound card if MIDI interfaces are removed.
450                         // This means that as long as ALSA is used for audio, MIDI 1.0 USB
451                         // devices should use the ALSA path for MIDI.
452                         if (parser.containsLegacyMidiDeviceEndpoint()) {
453                             midiDevice = UsbDirectMidiDevice.create(mContext,
454                                     newDevice, parser, false, uniqueUsbDeviceIdentifier);
455                             if (midiDevice != null) {
456                                 midiDevices.add(midiDevice);
457                             } else {
458                                 Slog.e(TAG, "Legacy Midi Device is null.");
459                             }
460                         }
461                     }
462 
463                     if (!midiDevices.isEmpty()) {
464                         mMidiDevices.put(deviceAddress, midiDevices);
465                     }
466                 }
467 
468                 // Tracking
469                 addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
470                         parser.getRawDescriptors());
471 
472                 // Stats collection
473                 FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED,
474                         newDevice.getVendorId(), newDevice.getProductId(),
475                         parser.hasAudioInterface(), parser.hasHIDInterface(),
476                         parser.hasStorageInterface(),
477                         FrameworkStatsLog.USB_DEVICE_ATTACHED__STATE__STATE_CONNECTED, 0);
478             }
479         }
480 
481         if (DEBUG) {
482             Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
483         }
484 
485         return true;
486     }
487 
488     /* Called from JNI in monitorUsbHostBus to report USB device removal */
489     @SuppressWarnings("unused")
usbDeviceRemoved(String deviceAddress)490     private void usbDeviceRemoved(String deviceAddress) {
491         if (DEBUG) {
492             Slog.d(TAG, "usbDeviceRemoved(" + deviceAddress + ") end");
493         }
494 
495         synchronized (mLock) {
496             UsbDevice device = mDevices.remove(deviceAddress);
497             if (device != null) {
498                 Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName());
499                 mUsbAlsaManager.usbDeviceRemoved(deviceAddress);
500                 mPermissionManager.usbDeviceRemoved(device);
501 
502                 // MIDI
503                 ArrayList<UsbDirectMidiDevice> midiDevices =
504                         mMidiDevices.remove(deviceAddress);
505                 if (midiDevices != null) {
506                     for (UsbDirectMidiDevice midiDevice : midiDevices) {
507                         if (midiDevice != null) {
508                             IoUtils.closeQuietly(midiDevice);
509                         }
510                     }
511                     Slog.i(TAG, "USB MIDI Devices Removed: " + deviceAddress);
512                 }
513 
514                 getCurrentUserSettings().usbDeviceRemoved(device);
515                 ConnectionRecord current = mConnected.get(deviceAddress);
516                 // Tracking
517                 addConnectionRecord(deviceAddress, ConnectionRecord.DISCONNECT, null);
518 
519                 if (current != null) {
520                     UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress,
521                             current.mDescriptors);
522                         // Stats collection
523                     FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED,
524                             device.getVendorId(), device.getProductId(), parser.hasAudioInterface(),
525                             parser.hasHIDInterface(),  parser.hasStorageInterface(),
526                             FrameworkStatsLog.USB_DEVICE_ATTACHED__STATE__STATE_DISCONNECTED,
527                             System.currentTimeMillis() - current.mTimestamp);
528                 }
529             } else {
530                 Slog.d(TAG, "Removed device at " + deviceAddress + " was already gone");
531             }
532         }
533     }
534 
systemReady()535     public void systemReady() {
536         synchronized (mLock) {
537             // Create a thread to call into native code to wait for USB host events.
538             // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
539             Runnable runnable = this::monitorUsbHostBus;
540             new Thread(null, runnable, "UsbService host thread").start();
541         }
542     }
543 
544     /* Returns a list of all currently attached USB devices */
getDeviceList(Bundle devices)545     public void getDeviceList(Bundle devices) {
546         synchronized (mLock) {
547             for (String name : mDevices.keySet()) {
548                 devices.putParcelable(name, mDevices.get(name));
549             }
550         }
551     }
552 
553     /**
554      *  Opens the specified USB device
555      */
openDevice(String deviceAddress, UsbUserPermissionManager permissions, String packageName, int pid, int uid)556     public ParcelFileDescriptor openDevice(String deviceAddress,
557             UsbUserPermissionManager permissions, String packageName, int pid, int uid) {
558         synchronized (mLock) {
559             if (isDenyListed(deviceAddress)) {
560                 throw new SecurityException("USB device is on a restricted bus");
561             }
562             UsbDevice device = mDevices.get(deviceAddress);
563             if (device == null) {
564                 // if it is not in mDevices, it either does not exist or is denylisted
565                 throw new IllegalArgumentException(
566                         "device " + deviceAddress + " does not exist or is restricted");
567             }
568 
569             permissions.checkPermission(device, packageName, pid, uid);
570             return nativeOpenDevice(deviceAddress);
571         }
572     }
573 
574     /**
575      * Dump out various information about the state of USB device connections.
576      */
dump(DualDumpOutputStream dump, String idName, long id)577     public void dump(DualDumpOutputStream dump, String idName, long id) {
578         long token = dump.start(idName, id);
579 
580         synchronized (mHandlerLock) {
581             if (mUsbDeviceConnectionHandler != null) {
582                 writeComponentName(dump, "default_usb_host_connection_handler",
583                         UsbHostManagerProto.DEFAULT_USB_HOST_CONNECTION_HANDLER,
584                         mUsbDeviceConnectionHandler);
585             }
586         }
587         synchronized (mLock) {
588             for (String name : mDevices.keySet()) {
589                 writeDevice(dump, "devices", UsbHostManagerProto.DEVICES, mDevices.get(name));
590             }
591 
592             dump.write("num_connects", UsbHostManagerProto.NUM_CONNECTS, mNumConnects);
593 
594             for (ConnectionRecord rec : mConnections) {
595                 rec.dump(dump, "connections", UsbHostManagerProto.CONNECTIONS);
596             }
597 
598             for (ArrayList<UsbDirectMidiDevice> directMidiDevices : mMidiDevices.values()) {
599                 for (UsbDirectMidiDevice directMidiDevice : directMidiDevices) {
600                     directMidiDevice.dump(dump, "midi_devices", UsbHostManagerProto.MIDI_DEVICES);
601                 }
602             }
603         }
604 
605         dump.end(token);
606     }
607 
608     /**
609      * Dump various descriptor data.
610      */
dumpDescriptors(IndentingPrintWriter pw, String[] args)611     public void dumpDescriptors(IndentingPrintWriter pw, String[] args) {
612         if (mLastConnect != null) {
613             pw.println("Last Connected USB Device:");
614             if (args.length <= 1 || args[1].equals("-dump-short")) {
615                 mLastConnect.dumpShort(pw);
616             } else if (args[1].equals("-dump-tree")) {
617                 mLastConnect.dumpTree(pw);
618             } else if (args[1].equals("-dump-list")) {
619                 mLastConnect.dumpList(pw);
620             }  else if (args[1].equals("-dump-raw")) {
621                 mLastConnect.dumpRaw(pw);
622             }
623         } else {
624             pw.println("No USB Devices have been connected.");
625         }
626     }
627 
checkUsbInterfacesDenyListed(UsbDescriptorParser parser)628     private boolean checkUsbInterfacesDenyListed(UsbDescriptorParser parser) {
629         // Device class needs to be obtained through the device interface.  Ignore device only
630         // if ALL interfaces are deny-listed.
631         boolean shouldIgnoreDevice = false;
632         for (UsbDescriptor descriptor: parser.getDescriptors()) {
633             if (!(descriptor instanceof UsbInterfaceDescriptor)) {
634                 continue;
635             }
636             UsbInterfaceDescriptor iface = (UsbInterfaceDescriptor) descriptor;
637             shouldIgnoreDevice = isDenyListed(iface.getUsbClass(), iface.getUsbSubclass());
638             if (!shouldIgnoreDevice) {
639                 break;
640             }
641         }
642         if (shouldIgnoreDevice) {
643             if (DEBUG) {
644                 Slog.d(TAG, "usb interface class is deny listed");
645             }
646             return false;
647         }
648         return true;
649     }
650 
651     // Generate a 3 digit code.
generateNewUsbDeviceIdentifier()652     private String generateNewUsbDeviceIdentifier() {
653         String code;
654         int numberOfAttempts = 0;
655         do {
656             if (numberOfAttempts > MAX_UNIQUE_CODE_GENERATION_ATTEMPTS) {
657                 Slog.w(TAG, "MIDI unique code array resetting");
658                 mMidiUniqueCodes.clear();
659                 numberOfAttempts = 0;
660             }
661             code = "";
662             for (int i = 0; i < 3; i++) {
663                 code += mRandom.nextInt(10);
664             }
665             numberOfAttempts++;
666         } while (mMidiUniqueCodes.contains(code));
667         mMidiUniqueCodes.add(code);
668         return code;
669     }
670 
monitorUsbHostBus()671     private native void monitorUsbHostBus();
nativeOpenDevice(String deviceAddress)672     private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
673 }
674