1 /*
2  * Copyright (C) 2017 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 and
14  * limitations under the License.
15  */
16 package com.android.server.usb.descriptors;
17 
18 import android.hardware.usb.UsbDevice;
19 import android.util.Log;
20 
21 import java.util.ArrayList;
22 
23 /**
24  * @hide
25  * Class for parsing a binary stream of USB Descriptors.
26  */
27 public final class UsbDescriptorParser {
28     private static final String TAG = "UsbDescriptorParser";
29     public static final boolean DEBUG = false;
30 
31     private final String mDeviceAddr;
32 
33     private static final int MS_MIDI_1_0 = 0x0100;
34     private static final int MS_MIDI_2_0 = 0x0200;
35 
36     // Descriptor Objects
37     private static final int DESCRIPTORS_ALLOC_SIZE = 128;
38     private final ArrayList<UsbDescriptor> mDescriptors;
39 
40     private UsbDeviceDescriptor mDeviceDescriptor;
41     private UsbConfigDescriptor mCurConfigDescriptor;
42     private UsbInterfaceDescriptor mCurInterfaceDescriptor;
43     private UsbEndpointDescriptor mCurEndpointDescriptor;
44 
45     // The AudioClass spec implemented by the AudioClass Interfaces
46     // This may well be different than the overall USB Spec.
47     // Obtained from the first AudioClass Header descriptor.
48     private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
49 
50     // The VideoClass spec implemented by the VideoClass Interfaces
51     // This may well be different than the overall USB Spec.
52     // Obtained from the first VidieoClass Header descriptor.
53     private int mVCInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
54 
55     /**
56      * Connect this parser to an existing set of already parsed descriptors.
57      * This is useful for reporting.
58      */
UsbDescriptorParser(String deviceAddr, ArrayList<UsbDescriptor> descriptors)59     public UsbDescriptorParser(String deviceAddr, ArrayList<UsbDescriptor> descriptors) {
60         mDeviceAddr = deviceAddr;
61         mDescriptors = descriptors;
62         //TODO some error checking here....
63         mDeviceDescriptor = (UsbDeviceDescriptor) descriptors.get(0);
64     }
65 
66     /**
67      * Connect this parser to an byte array containing unparsed (raw) device descriptors
68      * to be parsed (and parse them). Useful for parsing a stored descriptor buffer.
69      */
UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors)70     public UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors) {
71         mDeviceAddr = deviceAddr;
72         mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE);
73         parseDescriptors(rawDescriptors);
74     }
75 
getDeviceAddr()76     public String getDeviceAddr() {
77         return mDeviceAddr;
78     }
79 
80     /**
81      * @return the USB Spec value associated with the Device descriptor for the
82      * descriptors stream being parsed.
83      *
84      * @throws IllegalArgumentException
85      */
getUsbSpec()86     public int getUsbSpec() {
87         if (mDeviceDescriptor != null) {
88             return mDeviceDescriptor.getSpec();
89         } else {
90             throw new IllegalArgumentException();
91         }
92     }
93 
setACInterfaceSpec(int spec)94     public void setACInterfaceSpec(int spec) {
95         mACInterfacesSpec = spec;
96     }
97 
getACInterfaceSpec()98     public int getACInterfaceSpec() {
99         return mACInterfacesSpec;
100     }
101 
setVCInterfaceSpec(int spec)102     public void setVCInterfaceSpec(int spec) {
103         mVCInterfacesSpec = spec;
104     }
105 
getVCInterfaceSpec()106     public int getVCInterfaceSpec() {
107         return mVCInterfacesSpec;
108     }
109 
110     private class UsbDescriptorsStreamFormatException extends Exception {
111         String mMessage;
UsbDescriptorsStreamFormatException(String message)112         UsbDescriptorsStreamFormatException(String message) {
113             mMessage = message;
114         }
115 
toString()116         public String toString() {
117             return "Descriptor Stream Format Exception: " + mMessage;
118         }
119     }
120 
121     /**
122      * The probability (as returned by getHeadsetProbability() at which we conclude
123      * the peripheral is a headset.
124      */
125     private static final float IN_HEADSET_TRIGGER = 0.75f;
126     private static final float OUT_HEADSET_TRIGGER = 0.75f;
127 
allocDescriptor(ByteStream stream)128     private UsbDescriptor allocDescriptor(ByteStream stream)
129             throws UsbDescriptorsStreamFormatException {
130         stream.resetReadCount();
131 
132         int length = stream.getUnsignedByte();
133         byte type = stream.getByte();
134 
135         UsbDescriptor.logDescriptorName(type, length);
136 
137         UsbDescriptor descriptor = null;
138         switch (type) {
139             /*
140              * Standard
141              */
142             case UsbDescriptor.DESCRIPTORTYPE_DEVICE:
143                 descriptor = mDeviceDescriptor = new UsbDeviceDescriptor(length, type);
144                 break;
145 
146             case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
147                 descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type);
148                 if (mDeviceDescriptor != null) {
149                     mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor);
150                 } else {
151                     Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!");
152                     throw new UsbDescriptorsStreamFormatException(
153                             "Config Descriptor found with no associated Device Descriptor!");
154                 }
155                 break;
156 
157             case UsbDescriptor.DESCRIPTORTYPE_INTERFACE:
158                 descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type);
159                 if (mCurConfigDescriptor != null) {
160                     mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor);
161                 } else {
162                     Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!");
163                     throw new UsbDescriptorsStreamFormatException(
164                             "Interface Descriptor found with no associated Config Descriptor!");
165                 }
166                 break;
167 
168             case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
169                 descriptor = mCurEndpointDescriptor = new UsbEndpointDescriptor(length, type);
170                 if (mCurInterfaceDescriptor != null) {
171                     mCurInterfaceDescriptor.addEndpointDescriptor(
172                             (UsbEndpointDescriptor) descriptor);
173                 } else {
174                     Log.e(TAG,
175                             "Endpoint Descriptor found with no associated Interface Descriptor!");
176                     throw new UsbDescriptorsStreamFormatException(
177                             "Endpoint Descriptor found with no associated Interface Descriptor!");
178                 }
179                 break;
180 
181             /*
182              * HID
183              */
184             case UsbDescriptor.DESCRIPTORTYPE_HID:
185                 descriptor = new UsbHIDDescriptor(length, type);
186                 break;
187 
188             /*
189              * Other
190              */
191             case UsbDescriptor.DESCRIPTORTYPE_INTERFACEASSOC:
192                 descriptor = new UsbInterfaceAssoc(length, type);
193                 break;
194 
195             /*
196              * Various Class Specific
197              */
198             case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE:
199                 if (mCurInterfaceDescriptor != null) {
200                     switch (mCurInterfaceDescriptor.getUsbClass()) {
201                         case UsbDescriptor.CLASSID_AUDIO:
202                             descriptor =
203                                     UsbACInterface.allocDescriptor(this, stream, length, type);
204                             if (descriptor instanceof UsbMSMidiHeader) {
205                                 mCurInterfaceDescriptor.setMidiHeaderInterfaceDescriptor(
206                                         descriptor);
207                             }
208                             break;
209 
210                         case UsbDescriptor.CLASSID_VIDEO:
211                             if (DEBUG) {
212                                 Log.d(TAG, "  UsbDescriptor.CLASSID_VIDEO");
213                             }
214                             descriptor =
215                                     UsbVCInterface.allocDescriptor(this, stream, length, type);
216                             break;
217 
218                         case UsbDescriptor.CLASSID_AUDIOVIDEO:
219                             if (DEBUG) {
220                                 Log.d(TAG, "  UsbDescriptor.CLASSID_AUDIOVIDEO");
221                             }
222                             break;
223 
224                         default:
225                             Log.w(TAG, "  Unparsed Class-specific");
226                             break;
227                     }
228                 }
229                 break;
230 
231             case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT:
232                 if (mCurInterfaceDescriptor != null) {
233                     int subClass = mCurInterfaceDescriptor.getUsbClass();
234                     switch (subClass) {
235                         case UsbDescriptor.CLASSID_AUDIO: {
236                             Byte subType = stream.getByte();
237                             if (DEBUG) {
238                                 Log.d(TAG, "UsbDescriptor.CLASSID_AUDIO type:0x"
239                                         + Integer.toHexString(type));
240                             }
241                             descriptor = UsbACEndpoint.allocDescriptor(this, length, type,
242                                     subType);
243                         }
244                             break;
245 
246                         case UsbDescriptor.CLASSID_VIDEO: {
247                             Byte subType = stream.getByte();
248                             if (DEBUG) {
249                                 Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO type:0x"
250                                         + Integer.toHexString(type));
251                             }
252                             descriptor = UsbVCEndpoint.allocDescriptor(this, length, type,
253                                     subType);
254                         }
255                             break;
256 
257                         case UsbDescriptor.CLASSID_AUDIOVIDEO:
258                             if (DEBUG) {
259                                 Log.d(TAG, "UsbDescriptor.CLASSID_AUDIOVIDEO type:0x"
260                                         + Integer.toHexString(type));
261                             }
262                             break;
263 
264                         default:
265                             Log.w(TAG, "  Unparsed Class-specific Endpoint:0x"
266                                     + Integer.toHexString(subClass));
267                             break;
268                     }
269                     if (mCurEndpointDescriptor != null && descriptor != null) {
270                         mCurEndpointDescriptor.setClassSpecificEndpointDescriptor(descriptor);
271                     }
272                 }
273                 break;
274 
275             default:
276                 break;
277         }
278 
279         if (descriptor == null) {
280             // Unknown Descriptor
281             descriptor = new UsbUnknown(length, type);
282         }
283 
284         return descriptor;
285     }
286 
getDeviceDescriptor()287     public UsbDeviceDescriptor getDeviceDescriptor() {
288         return mDeviceDescriptor;
289     }
290 
getCurInterface()291     public UsbInterfaceDescriptor getCurInterface() {
292         return mCurInterfaceDescriptor;
293     }
294 
295     /**
296      * @hide
297      */
parseDescriptors(byte[] descriptors)298     public void parseDescriptors(byte[] descriptors) {
299         ByteStream stream = new ByteStream(descriptors);
300         while (stream.available() > 0) {
301             UsbDescriptor descriptor = null;
302             try {
303                 descriptor = allocDescriptor(stream);
304             } catch (Exception ex) {
305                 Log.e(TAG, "Exception allocating USB descriptor.", ex);
306             }
307 
308             if (descriptor != null) {
309                 // Parse
310                 try {
311                     descriptor.parseRawDescriptors(stream);
312 
313                     // Clean up
314                     descriptor.postParse(stream);
315                 } catch (Exception ex) {
316                     // Clean up, compute error status
317                     descriptor.postParse(stream);
318 
319                     // Report
320                     Log.w(TAG, "Exception parsing USB descriptors. type:0x" + descriptor.getType()
321                             + " status:" + descriptor.getStatus());
322                     if (DEBUG) {
323                         // Show full stack trace if debugging
324                         Log.e(TAG, "Exception parsing USB descriptors.", ex);
325                     }
326                     StackTraceElement[] stackElems = ex.getStackTrace();
327                     if (stackElems.length > 0) {
328                         Log.i(TAG, "  class:" + stackElems[0].getClassName()
329                                     + " @ " + stackElems[0].getLineNumber());
330                     }
331                     if (stackElems.length > 1) {
332                         Log.i(TAG, "  class:" + stackElems[1].getClassName()
333                                 + " @ " + stackElems[1].getLineNumber());
334                     }
335 
336                     // Finish up
337                     descriptor.setStatus(UsbDescriptor.STATUS_PARSE_EXCEPTION);
338                 } finally {
339                     mDescriptors.add(descriptor);
340                 }
341             }
342         }
343         if (DEBUG) {
344             Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors.");
345         }
346     }
347 
getRawDescriptors()348     public byte[] getRawDescriptors() {
349         return getRawDescriptors_native(mDeviceAddr);
350     }
351 
getRawDescriptors_native(String deviceAddr)352     private native byte[] getRawDescriptors_native(String deviceAddr);
353 
354     /**
355      * @hide
356      */
getDescriptorString(int stringId)357     public String getDescriptorString(int stringId) {
358         return getDescriptorString_native(mDeviceAddr, stringId);
359     }
360 
getDescriptorString_native(String deviceAddr, int stringId)361     private native String getDescriptorString_native(String deviceAddr, int stringId);
362 
getParsingSpec()363     public int getParsingSpec() {
364         return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
365     }
366 
getDescriptors()367     public ArrayList<UsbDescriptor> getDescriptors() {
368         return mDescriptors;
369     }
370 
371     /**
372      * @hide
373      */
toAndroidUsbDeviceBuilder()374     public UsbDevice.Builder toAndroidUsbDeviceBuilder() {
375         if (mDeviceDescriptor == null) {
376             Log.e(TAG, "toAndroidUsbDevice() ERROR - No Device Descriptor");
377             return null;
378         }
379 
380         UsbDevice.Builder builder = mDeviceDescriptor.toAndroid(this);
381         if (builder == null) {
382             Log.e(TAG, "toAndroidUsbDevice() ERROR Creating Device");
383         }
384         return builder;
385     }
386 
387     /**
388      * @hide
389      */
getDescriptors(byte type)390     public ArrayList<UsbDescriptor> getDescriptors(byte type) {
391         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
392         for (UsbDescriptor descriptor : mDescriptors) {
393             if (descriptor.getType() == type) {
394                 list.add(descriptor);
395             }
396         }
397         return list;
398     }
399 
400     /**
401      * @hide
402      */
getInterfaceDescriptorsForClass(int usbClass)403     public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) {
404         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
405         for (UsbDescriptor descriptor : mDescriptors) {
406             // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE
407             if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_INTERFACE) {
408                 if (descriptor instanceof UsbInterfaceDescriptor) {
409                     UsbInterfaceDescriptor intrDesc = (UsbInterfaceDescriptor) descriptor;
410                     if (intrDesc.getUsbClass() == usbClass) {
411                         list.add(descriptor);
412                     }
413                 } else {
414                     Log.w(TAG, "Unrecognized Interface l: " + descriptor.getLength()
415                             + " t:0x" + Integer.toHexString(descriptor.getType()));
416                 }
417             }
418         }
419         return list;
420     }
421 
422     /**
423      * @hide
424      */
getACInterfaceDescriptors(byte subtype, int subclass)425     public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) {
426         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
427         for (UsbDescriptor descriptor : mDescriptors) {
428             if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE) {
429                 // ensure that this isn't an unrecognized DESCRIPTORTYPE_CLASSSPECIFIC_INTERFACE
430                 if (descriptor instanceof UsbACInterface) {
431                     UsbACInterface acDescriptor = (UsbACInterface) descriptor;
432                     if (acDescriptor.getSubtype() == subtype
433                             && acDescriptor.getSubclass() == subclass) {
434                         list.add(descriptor);
435                     }
436                 } else {
437                     Log.w(TAG, "Unrecognized Audio Interface len: " + descriptor.getLength()
438                             + " type:0x" + Integer.toHexString(descriptor.getType()));
439                 }
440             }
441         }
442         return list;
443     }
444 
445     /*
446      * Attribute predicates
447      */
448     /**
449      * @hide
450      */
hasInput()451     public boolean hasInput() {
452         if (DEBUG) {
453             Log.d(TAG, "---- hasInput()");
454         }
455         ArrayList<UsbDescriptor> acDescriptors =
456                 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
457                 UsbACInterface.AUDIO_AUDIOCONTROL);
458         boolean hasInput = false;
459         for (UsbDescriptor descriptor : acDescriptors) {
460             if (descriptor instanceof UsbACTerminal) {
461                 UsbACTerminal inDescr = (UsbACTerminal) descriptor;
462                 // Check for input and bi-directional terminal types
463                 int type = inDescr.getTerminalType();
464                 if (DEBUG) {
465                     Log.d(TAG, "  type:0x" + Integer.toHexString(type));
466                 }
467                 int terminalCategory = type & ~0xFF;
468                 if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED
469                         && terminalCategory != UsbTerminalTypes.TERMINAL_OUT_UNDEFINED) {
470                     // If not explicitly a USB connection or output, it could be an input.
471                     hasInput = true;
472                     break;
473                 }
474             } else {
475                 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength()
476                         + " t:0x" + Integer.toHexString(descriptor.getType()));
477             }
478         }
479 
480         if (DEBUG) {
481             Log.d(TAG, "hasInput() = " + hasInput);
482         }
483         return hasInput;
484     }
485 
486     /**
487      * @hide
488      */
hasOutput()489     public boolean hasOutput() {
490         if (DEBUG) {
491             Log.d(TAG, "---- hasOutput()");
492         }
493         ArrayList<UsbDescriptor> acDescriptors =
494                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
495                 UsbACInterface.AUDIO_AUDIOCONTROL);
496         boolean hasOutput = false;
497         for (UsbDescriptor descriptor : acDescriptors) {
498             if (descriptor instanceof UsbACTerminal) {
499                 UsbACTerminal outDescr = (UsbACTerminal) descriptor;
500                 // Check for output and bi-directional terminal types
501                 int type = outDescr.getTerminalType();
502                 if (DEBUG) {
503                     Log.d(TAG, "  type:0x" + Integer.toHexString(type));
504                 }
505                 int terminalCategory = type & ~0xFF;
506                 if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED
507                         && terminalCategory != UsbTerminalTypes.TERMINAL_IN_UNDEFINED) {
508                     // If not explicitly a USB connection or input, it could be an output.
509                     hasOutput = true;
510                     break;
511                 }
512             } else {
513                 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength()
514                         + " t:0x" + Integer.toHexString(descriptor.getType()));
515             }
516         }
517         if (DEBUG) {
518             Log.d(TAG, "hasOutput() = " + hasOutput);
519         }
520         return hasOutput;
521     }
522 
523     /**
524      * @hide
525      */
hasMic()526     public boolean hasMic() {
527         boolean hasMic = false;
528 
529         ArrayList<UsbDescriptor> acDescriptors =
530                 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
531                 UsbACInterface.AUDIO_AUDIOCONTROL);
532         for (UsbDescriptor descriptor : acDescriptors) {
533             if (descriptor instanceof UsbACTerminal) {
534                 UsbACTerminal inDescr = (UsbACTerminal) descriptor;
535                 if (inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_IN_MIC
536                         || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET
537                         || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED
538                         || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_LINE) {
539                     hasMic = true;
540                     break;
541                 }
542             } else {
543                 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength()
544                         + " t:0x" + Integer.toHexString(descriptor.getType()));
545             }
546         }
547         return hasMic;
548     }
549 
550     /**
551      * @hide
552      */
hasSpeaker()553     public boolean hasSpeaker() {
554         boolean hasSpeaker = false;
555 
556         ArrayList<UsbDescriptor> acDescriptors =
557                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
558                         UsbACInterface.AUDIO_AUDIOCONTROL);
559         for (UsbDescriptor descriptor : acDescriptors) {
560             if (descriptor instanceof UsbACTerminal) {
561                 UsbACTerminal outDescr = (UsbACTerminal) descriptor;
562                 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER
563                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
564                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
565                     hasSpeaker = true;
566                     break;
567                 }
568             } else {
569                 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength()
570                         + " t:0x" + Integer.toHexString(descriptor.getType()));
571             }
572         }
573 
574         return hasSpeaker;
575     }
576 
577     /**
578      *@ hide
579      */
hasAudioInterface()580     public boolean hasAudioInterface() {
581         ArrayList<UsbDescriptor> descriptors =
582                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
583         return !descriptors.isEmpty();
584     }
585 
586     /**
587      * Returns true only if there is a terminal whose subtype and terminal type are the same as
588      * the given values.
589      * @hide
590      */
hasAudioTerminal(int subType, int terminalType)591     public boolean hasAudioTerminal(int subType, int terminalType) {
592         for (UsbDescriptor descriptor : mDescriptors) {
593             if (descriptor instanceof UsbACTerminal) {
594                 if (((UsbACTerminal) descriptor).getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL
595                         && ((UsbACTerminal) descriptor).getSubtype() == subType
596                         && ((UsbACTerminal) descriptor).getTerminalType() == terminalType) {
597                     return true;
598                 }
599             }
600         }
601         return false;
602     }
603 
604     /**
605      * Returns true only if there is an interface whose subtype is the same as the given one and
606      * terminal type is different from the given one.
607      * @hide
608      */
hasAudioTerminalExcludeType(int subType, int excludedTerminalType)609     public boolean hasAudioTerminalExcludeType(int subType, int excludedTerminalType) {
610         for (UsbDescriptor descriptor : mDescriptors) {
611             if (descriptor instanceof UsbACTerminal) {
612                 if (((UsbACTerminal) descriptor).getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL
613                         && ((UsbACTerminal) descriptor).getSubtype() == subType
614                         && ((UsbACTerminal) descriptor).getTerminalType() != excludedTerminalType) {
615                     return true;
616                 }
617             }
618         }
619         return false;
620     }
621 
622     /**
623      * @hide
624      */
hasAudioPlayback()625     public boolean hasAudioPlayback() {
626         return hasAudioTerminalExcludeType(
627                 UsbACInterface.ACI_OUTPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING)
628                 && hasAudioTerminal(
629                         UsbACInterface.ACI_INPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING);
630     }
631 
632     /**
633      * @hide
634      */
hasAudioCapture()635     public boolean hasAudioCapture() {
636         return hasAudioTerminalExcludeType(
637                 UsbACInterface.ACI_INPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING)
638                 && hasAudioTerminal(
639                         UsbACInterface.ACI_OUTPUT_TERMINAL,
640                         UsbTerminalTypes.TERMINAL_USB_STREAMING);
641     }
642 
643     /**
644      * @hide
645      */
hasVideoCapture()646     public boolean hasVideoCapture() {
647         for (UsbDescriptor descriptor : mDescriptors) {
648             if (descriptor instanceof UsbVCInputTerminal) {
649                 return true;
650             }
651         }
652         return false;
653     }
654 
655     /**
656      * @hide
657      */
hasVideoPlayback()658     public boolean hasVideoPlayback() {
659         for (UsbDescriptor descriptor : mDescriptors) {
660             if (descriptor instanceof UsbVCOutputTerminal) {
661                 return true;
662             }
663         }
664         return false;
665     }
666 
667     /**
668      * @hide
669      */
hasHIDInterface()670     public boolean hasHIDInterface() {
671         ArrayList<UsbDescriptor> descriptors =
672                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID);
673         return !descriptors.isEmpty();
674     }
675 
676     /**
677      * @hide
678      */
hasStorageInterface()679     public boolean hasStorageInterface() {
680         ArrayList<UsbDescriptor> descriptors =
681                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_STORAGE);
682         return !descriptors.isEmpty();
683     }
684 
685     /**
686      * @hide
687      */
hasMIDIInterface()688     public boolean hasMIDIInterface() {
689         ArrayList<UsbDescriptor> descriptors =
690                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
691         for (UsbDescriptor descriptor : descriptors) {
692             // enusure that this isn't an unrecognized interface descriptor
693             if (descriptor instanceof UsbInterfaceDescriptor) {
694                 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
695                 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
696                     return true;
697                 }
698             } else {
699                 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
700                         + " t:0x" + Integer.toHexString(descriptor.getType()));
701             }
702         }
703         return false;
704     }
705 
706     /**
707      * @hide
708      */
containsUniversalMidiDeviceEndpoint()709     public boolean containsUniversalMidiDeviceEndpoint() {
710         ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
711                 findUniversalMidiInterfaceDescriptors();
712         return doesInterfaceContainEndpoint(interfaceDescriptors);
713     }
714 
715     /**
716      * @hide
717      */
containsLegacyMidiDeviceEndpoint()718     public boolean containsLegacyMidiDeviceEndpoint() {
719         ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
720                 findLegacyMidiInterfaceDescriptors();
721         return doesInterfaceContainEndpoint(interfaceDescriptors);
722     }
723 
724     /**
725      * @hide
726      */
doesInterfaceContainEndpoint( ArrayList<UsbInterfaceDescriptor> interfaceDescriptors)727     public boolean doesInterfaceContainEndpoint(
728             ArrayList<UsbInterfaceDescriptor> interfaceDescriptors) {
729         int outputCount = 0;
730         int inputCount = 0;
731         for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size();
732                 interfaceIndex++) {
733             UsbInterfaceDescriptor interfaceDescriptor = interfaceDescriptors.get(interfaceIndex);
734             for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
735                     endpointIndex++) {
736                 UsbEndpointDescriptor endpoint =
737                         interfaceDescriptor.getEndpointDescriptor(endpointIndex);
738                 // 0 is output, 1 << 7 is input.
739                 if (endpoint.getDirection() == 0) {
740                     outputCount++;
741                 } else {
742                     inputCount++;
743                 }
744             }
745         }
746         return (outputCount > 0) || (inputCount > 0);
747     }
748 
749     /**
750      * @hide
751      */
findUniversalMidiInterfaceDescriptors()752     public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() {
753         return findMidiInterfaceDescriptors(MS_MIDI_2_0);
754     }
755 
756     /**
757      * @hide
758      */
findLegacyMidiInterfaceDescriptors()759     public ArrayList<UsbInterfaceDescriptor> findLegacyMidiInterfaceDescriptors() {
760         return findMidiInterfaceDescriptors(MS_MIDI_1_0);
761     }
762 
763     /**
764      * @hide
765      */
findMidiInterfaceDescriptors(int type)766     private ArrayList<UsbInterfaceDescriptor> findMidiInterfaceDescriptors(int type) {
767         int count = 0;
768         ArrayList<UsbDescriptor> descriptors =
769                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
770         ArrayList<UsbInterfaceDescriptor> midiInterfaces =
771                 new ArrayList<UsbInterfaceDescriptor>();
772 
773         for (UsbDescriptor descriptor : descriptors) {
774             // ensure that this isn't an unrecognized interface descriptor
775             if (descriptor instanceof UsbInterfaceDescriptor) {
776                 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
777                 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
778                     UsbDescriptor midiHeaderDescriptor =
779                             interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
780                     if (midiHeaderDescriptor != null) {
781                         if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
782                             UsbMSMidiHeader midiHeader =
783                                     (UsbMSMidiHeader) midiHeaderDescriptor;
784                             if (midiHeader.getMidiStreamingClass() == type) {
785                                 midiInterfaces.add(interfaceDescriptor);
786                             }
787                         }
788                     }
789                 }
790             } else {
791                 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
792                         + " t:0x" + Integer.toHexString(descriptor.getType()));
793             }
794         }
795         return midiInterfaces;
796     }
797 
798     /**
799      * @hide
800      */
calculateMidiInterfaceDescriptorsCount()801     public int calculateMidiInterfaceDescriptorsCount() {
802         int count = 0;
803         ArrayList<UsbDescriptor> descriptors =
804                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
805         for (UsbDescriptor descriptor : descriptors) {
806             // ensure that this isn't an unrecognized interface descriptor
807             if (descriptor instanceof UsbInterfaceDescriptor) {
808                 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
809                 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
810                     UsbDescriptor midiHeaderDescriptor =
811                             interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
812                     if (midiHeaderDescriptor != null) {
813                         if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
814                             UsbMSMidiHeader midiHeader =
815                                     (UsbMSMidiHeader) midiHeaderDescriptor;
816                             count++;
817                         }
818                     }
819                 }
820             } else {
821                 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
822                         + " t:0x" + Integer.toHexString(descriptor.getType()));
823             }
824         }
825         return count;
826     }
827 
828     /**
829      * @hide
830      */
calculateNumLegacyMidiPorts(boolean isOutput)831     private int calculateNumLegacyMidiPorts(boolean isOutput) {
832         // Only look at the first config.
833         UsbConfigDescriptor configDescriptor = null;
834         for (UsbDescriptor descriptor : mDescriptors) {
835             if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CONFIG) {
836                 if (descriptor instanceof UsbConfigDescriptor) {
837                     configDescriptor = (UsbConfigDescriptor) descriptor;
838                     break;
839                 } else {
840                     Log.w(TAG, "Unrecognized Config l: " + descriptor.getLength()
841                             + " t:0x" + Integer.toHexString(descriptor.getType()));
842                 }
843             }
844         }
845         if (configDescriptor == null) {
846             Log.w(TAG, "Config not found");
847             return 0;
848         }
849 
850         ArrayList<UsbInterfaceDescriptor> legacyMidiInterfaceDescriptors =
851                 new ArrayList<UsbInterfaceDescriptor>();
852         for (UsbInterfaceDescriptor interfaceDescriptor
853                 : configDescriptor.getInterfaceDescriptors()) {
854             if (interfaceDescriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO) {
855                 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
856                     UsbDescriptor midiHeaderDescriptor =
857                             interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
858                     if (midiHeaderDescriptor != null) {
859                         if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
860                             UsbMSMidiHeader midiHeader =
861                                     (UsbMSMidiHeader) midiHeaderDescriptor;
862                             if (midiHeader.getMidiStreamingClass() == MS_MIDI_1_0) {
863                                 legacyMidiInterfaceDescriptors.add(interfaceDescriptor);
864                             }
865                         }
866                     }
867                 }
868             }
869         }
870 
871         int count = 0;
872         for (UsbInterfaceDescriptor interfaceDescriptor : legacyMidiInterfaceDescriptors) {
873             for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
874                 UsbEndpointDescriptor endpoint =
875                         interfaceDescriptor.getEndpointDescriptor(i);
876                 // 0 is output, 1 << 7 is input.
877                 if ((endpoint.getDirection() == 0) == isOutput) {
878                     UsbDescriptor classSpecificEndpointDescriptor =
879                             endpoint.getClassSpecificEndpointDescriptor();
880                     if (classSpecificEndpointDescriptor != null
881                             && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
882                         UsbACMidi10Endpoint midiEndpoint =
883                                 (UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
884                         count += midiEndpoint.getNumJacks();
885                     }
886                 }
887             }
888         }
889         return count;
890     }
891 
892     /**
893      * @hide
894      */
calculateNumLegacyMidiInputs()895     public int calculateNumLegacyMidiInputs() {
896         return calculateNumLegacyMidiPorts(false /*isOutput*/);
897     }
898 
899     /**
900      * @hide
901      */
calculateNumLegacyMidiOutputs()902     public int calculateNumLegacyMidiOutputs() {
903         return calculateNumLegacyMidiPorts(true /*isOutput*/);
904     }
905 
906     /**
907      * @hide
908      */
getInputHeadsetProbability()909     public float getInputHeadsetProbability() {
910         if (hasMIDIInterface()) {
911             return 0.0f;
912         }
913 
914         float probability = 0.0f;
915 
916         // Look for a microphone
917         boolean hasMic = hasMic();
918 
919         // Look for a "speaker"
920         boolean hasSpeaker = hasSpeaker();
921 
922         if (hasMic && hasSpeaker) {
923             probability += 0.75f;
924         }
925 
926         if (hasMic && hasHIDInterface()) {
927             probability += 0.25f;
928         }
929 
930         return probability;
931     }
932 
933     /**
934      * getInputHeadsetProbability() reports a probability of a USB Input peripheral being a
935      * headset. The probability range is between 0.0f (definitely NOT a headset) and
936      * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
937      * to count on the peripheral being a headset.
938      */
isInputHeadset()939     public boolean isInputHeadset() {
940         return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
941     }
942 
943     // TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here.
getMaximumChannelCount()944     private int getMaximumChannelCount() {
945         int maxChannelCount = 0;
946         for (UsbDescriptor descriptor : mDescriptors) {
947             if (descriptor instanceof UsbAudioChannelCluster) {
948                 maxChannelCount = Math.max(maxChannelCount,
949                         ((UsbAudioChannelCluster) descriptor).getChannelCount());
950             }
951         }
952         return maxChannelCount;
953     }
954 
955     /**
956      * @hide
957      */
getOutputHeadsetLikelihood()958     public float getOutputHeadsetLikelihood() {
959         if (hasMIDIInterface()) {
960             return 0.0f;
961         }
962 
963         float likelihood = 0.0f;
964         ArrayList<UsbDescriptor> acDescriptors;
965 
966         // Look for a "speaker"
967         boolean hasSpeaker = false;
968         boolean hasAssociatedInputTerminal = false;
969         boolean hasHeadphoneOrHeadset = false;
970         acDescriptors =
971                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
972                         UsbACInterface.AUDIO_AUDIOCONTROL);
973         for (UsbDescriptor descriptor : acDescriptors) {
974             if (descriptor instanceof UsbACTerminal) {
975                 UsbACTerminal outDescr = (UsbACTerminal) descriptor;
976                 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER) {
977                     hasSpeaker = true;
978                     if (outDescr.getAssocTerminal() != 0x0) {
979                         hasAssociatedInputTerminal = true;
980                     }
981                 } else if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
982                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
983                     hasHeadphoneOrHeadset = true;
984                 }
985             } else {
986                 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength()
987                         + " t:0x" + Integer.toHexString(descriptor.getType()));
988             }
989         }
990 
991         if (hasHeadphoneOrHeadset) {
992             likelihood += 0.75f;
993         } else if (hasSpeaker) {
994             // The device only reports output terminal as speaker. Try to figure out if the device
995             // is a headset or not by checking if it has associated input terminal and if multiple
996             // channels are supported or not.
997             likelihood += 0.5f;
998             if (hasAssociatedInputTerminal) {
999                 likelihood += 0.25f;
1000             }
1001             if (getMaximumChannelCount() > 2) {
1002                 // When multiple channels are supported, it is less likely to be a headset.
1003                 likelihood -= 0.25f;
1004             }
1005         }
1006 
1007         if ((hasHeadphoneOrHeadset || hasSpeaker) && hasHIDInterface()) {
1008             likelihood += 0.25f;
1009         }
1010 
1011         return likelihood;
1012     }
1013 
1014     /**
1015      * getOutputHeadsetProbability() reports a probability of a USB Output peripheral being a
1016      * headset. The probability range is between 0.0f (definitely NOT a headset) and
1017      * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
1018      * to count on the peripheral being a headset.
1019      */
isOutputHeadset()1020     public boolean isOutputHeadset() {
1021         return getOutputHeadsetLikelihood() >= OUT_HEADSET_TRIGGER;
1022     }
1023 
1024     /**
1025      * isDock() indicates if the connected USB output peripheral is a docking station with
1026      * audio output.
1027      * A valid audio dock must declare only one audio output control terminal of type
1028      * TERMINAL_EXTERN_DIGITAL.
1029      */
isDock()1030     public boolean isDock() {
1031         if (hasMIDIInterface() || hasHIDInterface()) {
1032             return false;
1033         }
1034 
1035         ArrayList<UsbDescriptor> acDescriptors =
1036                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
1037                         UsbACInterface.AUDIO_AUDIOCONTROL);
1038 
1039         if (acDescriptors.size() != 1) {
1040             return false;
1041         }
1042 
1043         if (acDescriptors.get(0) instanceof UsbACTerminal) {
1044             UsbACTerminal outDescr = (UsbACTerminal) acDescriptors.get(0);
1045             if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL) {
1046                 return true;
1047             }
1048         } else {
1049             Log.w(TAG, "Undefined Audio Output terminal l: " + acDescriptors.get(0).getLength()
1050                     + " t:0x" + Integer.toHexString(acDescriptors.get(0).getType()));
1051         }
1052         return false;
1053     }
1054 
1055 }
1056