• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         ArrayList<UsbDescriptor> acDescriptors =
528                 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
529                 UsbACInterface.AUDIO_AUDIOCONTROL);
530         for (UsbDescriptor descriptor : acDescriptors) {
531             if (descriptor instanceof UsbACTerminal) {
532                 UsbACTerminal inDescr = (UsbACTerminal) descriptor;
533                 if (inDescr.isInputTerminal()) {
534                     return true;
535                 }
536             } else {
537                 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength()
538                         + " t:0x" + Integer.toHexString(descriptor.getType()));
539             }
540         }
541         return false;
542     }
543 
544     /**
545      * @hide
546      */
hasSpeaker()547     public boolean hasSpeaker() {
548         boolean hasSpeaker = false;
549 
550         ArrayList<UsbDescriptor> acDescriptors =
551                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
552                         UsbACInterface.AUDIO_AUDIOCONTROL);
553         for (UsbDescriptor descriptor : acDescriptors) {
554             if (descriptor instanceof UsbACTerminal) {
555                 UsbACTerminal outDescr = (UsbACTerminal) descriptor;
556                 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER
557                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
558                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
559                     hasSpeaker = true;
560                     break;
561                 }
562             } else {
563                 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength()
564                         + " t:0x" + Integer.toHexString(descriptor.getType()));
565             }
566         }
567 
568         return hasSpeaker;
569     }
570 
571     /**
572      *@ hide
573      */
hasAudioInterface()574     public boolean hasAudioInterface() {
575         ArrayList<UsbDescriptor> descriptors =
576                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
577         return !descriptors.isEmpty();
578     }
579 
580     /**
581      * Returns true only if there is a terminal whose subtype and terminal type are the same as
582      * the given values.
583      * @hide
584      */
hasAudioTerminal(int subType, int terminalType)585     public boolean hasAudioTerminal(int subType, int terminalType) {
586         for (UsbDescriptor descriptor : mDescriptors) {
587             if (descriptor instanceof UsbACTerminal) {
588                 if (((UsbACTerminal) descriptor).getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL
589                         && ((UsbACTerminal) descriptor).getSubtype() == subType
590                         && ((UsbACTerminal) descriptor).getTerminalType() == terminalType) {
591                     return true;
592                 }
593             }
594         }
595         return false;
596     }
597 
598     /**
599      * Returns true only if there is an interface whose subtype is the same as the given one and
600      * terminal type is different from the given one.
601      * @hide
602      */
hasAudioTerminalExcludeType(int subType, int excludedTerminalType)603     public boolean hasAudioTerminalExcludeType(int subType, int excludedTerminalType) {
604         for (UsbDescriptor descriptor : mDescriptors) {
605             if (descriptor instanceof UsbACTerminal) {
606                 if (((UsbACTerminal) descriptor).getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL
607                         && ((UsbACTerminal) descriptor).getSubtype() == subType
608                         && ((UsbACTerminal) descriptor).getTerminalType() != excludedTerminalType) {
609                     return true;
610                 }
611             }
612         }
613         return false;
614     }
615 
616     /**
617      * @hide
618      */
hasAudioPlayback()619     public boolean hasAudioPlayback() {
620         return hasAudioTerminalExcludeType(
621                 UsbACInterface.ACI_OUTPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING)
622                 && hasAudioTerminal(
623                         UsbACInterface.ACI_INPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING);
624     }
625 
626     /**
627      * @hide
628      */
hasAudioCapture()629     public boolean hasAudioCapture() {
630         return hasAudioTerminalExcludeType(
631                 UsbACInterface.ACI_INPUT_TERMINAL, UsbTerminalTypes.TERMINAL_USB_STREAMING)
632                 && hasAudioTerminal(
633                         UsbACInterface.ACI_OUTPUT_TERMINAL,
634                         UsbTerminalTypes.TERMINAL_USB_STREAMING);
635     }
636 
637     /**
638      * @hide
639      */
hasVideoCapture()640     public boolean hasVideoCapture() {
641         for (UsbDescriptor descriptor : mDescriptors) {
642             if (descriptor instanceof UsbVCInputTerminal) {
643                 return true;
644             }
645         }
646         return false;
647     }
648 
649     /**
650      * @hide
651      */
hasVideoPlayback()652     public boolean hasVideoPlayback() {
653         for (UsbDescriptor descriptor : mDescriptors) {
654             if (descriptor instanceof UsbVCOutputTerminal) {
655                 return true;
656             }
657         }
658         return false;
659     }
660 
661     /**
662      * @hide
663      */
hasHIDInterface()664     public boolean hasHIDInterface() {
665         ArrayList<UsbDescriptor> descriptors =
666                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID);
667         return !descriptors.isEmpty();
668     }
669 
670     /**
671      * @hide
672      */
hasStorageInterface()673     public boolean hasStorageInterface() {
674         ArrayList<UsbDescriptor> descriptors =
675                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_STORAGE);
676         return !descriptors.isEmpty();
677     }
678 
679     /**
680      * @hide
681      */
hasMIDIInterface()682     public boolean hasMIDIInterface() {
683         ArrayList<UsbDescriptor> descriptors =
684                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
685         for (UsbDescriptor descriptor : descriptors) {
686             // enusure that this isn't an unrecognized interface descriptor
687             if (descriptor instanceof UsbInterfaceDescriptor) {
688                 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
689                 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
690                     return true;
691                 }
692             } else {
693                 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
694                         + " t:0x" + Integer.toHexString(descriptor.getType()));
695             }
696         }
697         return false;
698     }
699 
700     /**
701      * @hide
702      */
containsUniversalMidiDeviceEndpoint()703     public boolean containsUniversalMidiDeviceEndpoint() {
704         ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
705                 findUniversalMidiInterfaceDescriptors();
706         return doesInterfaceContainEndpoint(interfaceDescriptors);
707     }
708 
709     /**
710      * @hide
711      */
containsLegacyMidiDeviceEndpoint()712     public boolean containsLegacyMidiDeviceEndpoint() {
713         ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
714                 findLegacyMidiInterfaceDescriptors();
715         return doesInterfaceContainEndpoint(interfaceDescriptors);
716     }
717 
718     /**
719      * @hide
720      */
doesInterfaceContainEndpoint( ArrayList<UsbInterfaceDescriptor> interfaceDescriptors)721     public boolean doesInterfaceContainEndpoint(
722             ArrayList<UsbInterfaceDescriptor> interfaceDescriptors) {
723         int outputCount = 0;
724         int inputCount = 0;
725         for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size();
726                 interfaceIndex++) {
727             UsbInterfaceDescriptor interfaceDescriptor = interfaceDescriptors.get(interfaceIndex);
728             for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
729                     endpointIndex++) {
730                 UsbEndpointDescriptor endpoint =
731                         interfaceDescriptor.getEndpointDescriptor(endpointIndex);
732                 // 0 is output, 1 << 7 is input.
733                 if (endpoint.getDirection() == 0) {
734                     outputCount++;
735                 } else {
736                     inputCount++;
737                 }
738             }
739         }
740         return (outputCount > 0) || (inputCount > 0);
741     }
742 
743     /**
744      * @hide
745      */
findUniversalMidiInterfaceDescriptors()746     public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() {
747         return findMidiInterfaceDescriptors(MS_MIDI_2_0);
748     }
749 
750     /**
751      * @hide
752      */
findLegacyMidiInterfaceDescriptors()753     public ArrayList<UsbInterfaceDescriptor> findLegacyMidiInterfaceDescriptors() {
754         return findMidiInterfaceDescriptors(MS_MIDI_1_0);
755     }
756 
757     /**
758      * @hide
759      */
findMidiInterfaceDescriptors(int type)760     private ArrayList<UsbInterfaceDescriptor> findMidiInterfaceDescriptors(int type) {
761         int count = 0;
762         ArrayList<UsbDescriptor> descriptors =
763                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
764         ArrayList<UsbInterfaceDescriptor> midiInterfaces =
765                 new ArrayList<UsbInterfaceDescriptor>();
766 
767         for (UsbDescriptor descriptor : descriptors) {
768             // ensure that this isn't an unrecognized interface descriptor
769             if (descriptor instanceof UsbInterfaceDescriptor) {
770                 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
771                 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
772                     UsbDescriptor midiHeaderDescriptor =
773                             interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
774                     if (midiHeaderDescriptor != null) {
775                         if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
776                             UsbMSMidiHeader midiHeader =
777                                     (UsbMSMidiHeader) midiHeaderDescriptor;
778                             if (midiHeader.getMidiStreamingClass() == type) {
779                                 midiInterfaces.add(interfaceDescriptor);
780                             }
781                         }
782                     }
783                 }
784             } else {
785                 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
786                         + " t:0x" + Integer.toHexString(descriptor.getType()));
787             }
788         }
789         return midiInterfaces;
790     }
791 
792     /**
793      * @hide
794      */
calculateMidiInterfaceDescriptorsCount()795     public int calculateMidiInterfaceDescriptorsCount() {
796         int count = 0;
797         ArrayList<UsbDescriptor> descriptors =
798                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
799         for (UsbDescriptor descriptor : descriptors) {
800             // ensure that this isn't an unrecognized interface descriptor
801             if (descriptor instanceof UsbInterfaceDescriptor) {
802                 UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
803                 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
804                     UsbDescriptor midiHeaderDescriptor =
805                             interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
806                     if (midiHeaderDescriptor != null) {
807                         if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
808                             UsbMSMidiHeader midiHeader =
809                                     (UsbMSMidiHeader) midiHeaderDescriptor;
810                             count++;
811                         }
812                     }
813                 }
814             } else {
815                 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
816                         + " t:0x" + Integer.toHexString(descriptor.getType()));
817             }
818         }
819         return count;
820     }
821 
822     /**
823      * @hide
824      */
calculateNumLegacyMidiPorts(boolean isOutput)825     private int calculateNumLegacyMidiPorts(boolean isOutput) {
826         // Only look at the first config.
827         UsbConfigDescriptor configDescriptor = null;
828         for (UsbDescriptor descriptor : mDescriptors) {
829             if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CONFIG) {
830                 if (descriptor instanceof UsbConfigDescriptor) {
831                     configDescriptor = (UsbConfigDescriptor) descriptor;
832                     break;
833                 } else {
834                     Log.w(TAG, "Unrecognized Config l: " + descriptor.getLength()
835                             + " t:0x" + Integer.toHexString(descriptor.getType()));
836                 }
837             }
838         }
839         if (configDescriptor == null) {
840             Log.w(TAG, "Config not found");
841             return 0;
842         }
843 
844         ArrayList<UsbInterfaceDescriptor> legacyMidiInterfaceDescriptors =
845                 new ArrayList<UsbInterfaceDescriptor>();
846         for (UsbInterfaceDescriptor interfaceDescriptor
847                 : configDescriptor.getInterfaceDescriptors()) {
848             if (interfaceDescriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO) {
849                 if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
850                     UsbDescriptor midiHeaderDescriptor =
851                             interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
852                     if (midiHeaderDescriptor != null) {
853                         if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
854                             UsbMSMidiHeader midiHeader =
855                                     (UsbMSMidiHeader) midiHeaderDescriptor;
856                             if (midiHeader.getMidiStreamingClass() == MS_MIDI_1_0) {
857                                 legacyMidiInterfaceDescriptors.add(interfaceDescriptor);
858                             }
859                         }
860                     }
861                 }
862             }
863         }
864 
865         int count = 0;
866         for (UsbInterfaceDescriptor interfaceDescriptor : legacyMidiInterfaceDescriptors) {
867             for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
868                 UsbEndpointDescriptor endpoint =
869                         interfaceDescriptor.getEndpointDescriptor(i);
870                 // 0 is output, 1 << 7 is input.
871                 if ((endpoint.getDirection() == 0) == isOutput) {
872                     UsbDescriptor classSpecificEndpointDescriptor =
873                             endpoint.getClassSpecificEndpointDescriptor();
874                     if (classSpecificEndpointDescriptor != null
875                             && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
876                         UsbACMidi10Endpoint midiEndpoint =
877                                 (UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
878                         count += midiEndpoint.getNumJacks();
879                     }
880                 }
881             }
882         }
883         return count;
884     }
885 
886     /**
887      * @hide
888      */
calculateNumLegacyMidiInputs()889     public int calculateNumLegacyMidiInputs() {
890         return calculateNumLegacyMidiPorts(false /*isOutput*/);
891     }
892 
893     /**
894      * @hide
895      */
calculateNumLegacyMidiOutputs()896     public int calculateNumLegacyMidiOutputs() {
897         return calculateNumLegacyMidiPorts(true /*isOutput*/);
898     }
899 
900     /**
901      * @hide
902      */
getInputHeadsetProbability()903     public float getInputHeadsetProbability() {
904         if (hasMIDIInterface()) {
905             return 0.0f;
906         }
907 
908         float probability = 0.0f;
909 
910         // Look for a "speaker"
911         boolean hasSpeaker = hasSpeaker();
912 
913         if (hasMic()) {
914             if (hasSpeaker) {
915                 probability += 0.75f;
916             }
917             if (hasHIDInterface()) {
918                 probability += 0.25f;
919             }
920             if (getMaximumInputChannelCount() > 1) {
921                 // A headset is more likely to only support mono capture.
922                 probability -= 0.25f;
923             }
924         }
925 
926         return probability;
927     }
928 
929     /**
930      * getInputHeadsetProbability() reports a probability of a USB Input peripheral being a
931      * headset. The probability range is between 0.0f (definitely NOT a headset) and
932      * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
933      * to count on the peripheral being a headset.
934      * To align with the output device type, only treat the device as input headset if it is
935      * an output headset.
936      */
isInputHeadset()937     public boolean isInputHeadset() {
938         return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER && isOutputHeadset();
939     }
940 
941     // TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here.
getMaximumChannelCount()942     private int getMaximumChannelCount() {
943         int maxChannelCount = 0;
944         for (UsbDescriptor descriptor : mDescriptors) {
945             if (descriptor instanceof UsbAudioChannelCluster) {
946                 maxChannelCount = Math.max(maxChannelCount,
947                         ((UsbAudioChannelCluster) descriptor).getChannelCount());
948             }
949         }
950         return maxChannelCount;
951     }
952 
getMaximumInputChannelCount()953     private int getMaximumInputChannelCount() {
954         int maxChannelCount = 0;
955         ArrayList<UsbDescriptor> acDescriptors =
956                 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
957                         UsbACInterface.AUDIO_AUDIOCONTROL);
958         for (UsbDescriptor descriptor : acDescriptors) {
959             if (!(descriptor instanceof UsbACTerminal)) {
960                 continue;
961             }
962             UsbACTerminal inDescr = (UsbACTerminal) descriptor;
963             if (!inDescr.isInputTerminal()) {
964                 continue;
965             }
966             // For an input terminal, it should at lease has 1 channel.
967             // Comparing the max channel count with 1 here in case the USB device doesn't report
968             // audio channel cluster.
969             maxChannelCount = Math.max(maxChannelCount, 1);
970             if (!(descriptor instanceof UsbAudioChannelCluster)) {
971                 continue;
972             }
973             maxChannelCount = Math.max(maxChannelCount,
974                     ((UsbAudioChannelCluster) descriptor).getChannelCount());
975         }
976         return maxChannelCount;
977     }
978 
979     /**
980      * @hide
981      */
getOutputHeadsetLikelihood()982     public float getOutputHeadsetLikelihood() {
983         if (hasMIDIInterface()) {
984             return 0.0f;
985         }
986 
987         float likelihood = 0.0f;
988         ArrayList<UsbDescriptor> acDescriptors;
989 
990         // Look for a "speaker"
991         boolean hasSpeaker = false;
992         boolean hasAssociatedInputTerminal = false;
993         boolean hasHeadphoneOrHeadset = false;
994         acDescriptors =
995                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
996                         UsbACInterface.AUDIO_AUDIOCONTROL);
997         for (UsbDescriptor descriptor : acDescriptors) {
998             if (descriptor instanceof UsbACTerminal) {
999                 UsbACTerminal outDescr = (UsbACTerminal) descriptor;
1000                 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER) {
1001                     hasSpeaker = true;
1002                     if (outDescr.getAssocTerminal() != 0x0) {
1003                         hasAssociatedInputTerminal = true;
1004                     }
1005                 } else if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
1006                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
1007                     hasHeadphoneOrHeadset = true;
1008                 }
1009             } else {
1010                 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength()
1011                         + " t:0x" + Integer.toHexString(descriptor.getType()));
1012             }
1013         }
1014 
1015         if (hasHeadphoneOrHeadset) {
1016             likelihood += 0.75f;
1017         } else if (hasSpeaker) {
1018             // The device only reports output terminal as speaker. Try to figure out if the device
1019             // is a headset or not by checking if it has associated input terminal and if multiple
1020             // channels are supported or not.
1021             likelihood += 0.5f;
1022             if (hasAssociatedInputTerminal) {
1023                 likelihood += 0.25f;
1024             }
1025             if (getMaximumChannelCount() > 2) {
1026                 // When multiple channels are supported, it is less likely to be a headset.
1027                 likelihood -= 0.25f;
1028             }
1029         }
1030 
1031         if ((hasHeadphoneOrHeadset || hasSpeaker) && hasHIDInterface()) {
1032             likelihood += 0.25f;
1033         }
1034 
1035         return likelihood;
1036     }
1037 
1038     /**
1039      * getOutputHeadsetProbability() reports a probability of a USB Output peripheral being a
1040      * headset. The probability range is between 0.0f (definitely NOT a headset) and
1041      * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
1042      * to count on the peripheral being a headset.
1043      */
isOutputHeadset()1044     public boolean isOutputHeadset() {
1045         return getOutputHeadsetLikelihood() >= OUT_HEADSET_TRIGGER;
1046     }
1047 
1048     /**
1049      * isDock() indicates if the connected USB output peripheral is a docking station with
1050      * audio output.
1051      * A valid audio dock must declare only one audio output control terminal of type
1052      * TERMINAL_EXTERN_DIGITAL.
1053      */
isDock()1054     public boolean isDock() {
1055         if (hasMIDIInterface() || hasHIDInterface()) {
1056             return false;
1057         }
1058 
1059         ArrayList<UsbDescriptor> acDescriptors =
1060                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
1061                         UsbACInterface.AUDIO_AUDIOCONTROL);
1062 
1063         if (acDescriptors.size() != 1) {
1064             return false;
1065         }
1066 
1067         if (acDescriptors.get(0) instanceof UsbACTerminal) {
1068             UsbACTerminal outDescr = (UsbACTerminal) acDescriptors.get(0);
1069             if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL) {
1070                 return true;
1071             }
1072         } else {
1073             Log.w(TAG, "Undefined Audio Output terminal l: " + acDescriptors.get(0).getLength()
1074                     + " t:0x" + Integer.toHexString(acDescriptors.get(0).getType()));
1075         }
1076         return false;
1077     }
1078 
1079 }
1080