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     private static final boolean DEBUG = false;
30 
31     private final String mDeviceAddr;
32 
33     // Descriptor Objects
34     private static final int DESCRIPTORS_ALLOC_SIZE = 128;
35     private final ArrayList<UsbDescriptor> mDescriptors;
36 
37     private UsbDeviceDescriptor mDeviceDescriptor;
38     private UsbConfigDescriptor mCurConfigDescriptor;
39     private UsbInterfaceDescriptor mCurInterfaceDescriptor;
40 
41     // The AudioClass spec implemented by the AudioClass Interfaces
42     // This may well be different than the overall USB Spec.
43     // Obtained from the first AudioClass Header descriptor.
44     private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
45 
46     /**
47      * Connect this parser to an existing set of already parsed descriptors.
48      * This is useful for reporting.
49      */
UsbDescriptorParser(String deviceAddr, ArrayList<UsbDescriptor> descriptors)50     public UsbDescriptorParser(String deviceAddr, ArrayList<UsbDescriptor> descriptors) {
51         mDeviceAddr = deviceAddr;
52         mDescriptors = descriptors;
53         //TODO some error checking here....
54         mDeviceDescriptor = (UsbDeviceDescriptor) descriptors.get(0);
55     }
56 
57     /**
58      * Connect this parser to an byte array containing unparsed (raw) device descriptors
59      * to be parsed (and parse them). Useful for parsing a stored descriptor buffer.
60      */
UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors)61     public UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors) {
62         mDeviceAddr = deviceAddr;
63         mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE);
64         parseDescriptors(rawDescriptors);
65     }
66 
getDeviceAddr()67     public String getDeviceAddr() {
68         return mDeviceAddr;
69     }
70 
71     /**
72      * @return the USB Spec value associated with the Device descriptor for the
73      * descriptors stream being parsed.
74      *
75      * @throws IllegalArgumentException
76      */
getUsbSpec()77     public int getUsbSpec() {
78         if (mDeviceDescriptor != null) {
79             return mDeviceDescriptor.getSpec();
80         } else {
81             throw new IllegalArgumentException();
82         }
83     }
84 
setACInterfaceSpec(int spec)85     public void setACInterfaceSpec(int spec) {
86         mACInterfacesSpec = spec;
87     }
88 
getACInterfaceSpec()89     public int getACInterfaceSpec() {
90         return mACInterfacesSpec;
91     }
92 
93     private class UsbDescriptorsStreamFormatException extends Exception {
94         String mMessage;
UsbDescriptorsStreamFormatException(String message)95         UsbDescriptorsStreamFormatException(String message) {
96             mMessage = message;
97         }
98 
toString()99         public String toString() {
100             return "Descriptor Stream Format Exception: " + mMessage;
101         }
102     }
103 
104     /**
105      * The probability (as returned by getHeadsetProbability() at which we conclude
106      * the peripheral is a headset.
107      */
108     private static final float IN_HEADSET_TRIGGER = 0.75f;
109     private static final float OUT_HEADSET_TRIGGER = 0.75f;
110 
allocDescriptor(ByteStream stream)111     private UsbDescriptor allocDescriptor(ByteStream stream)
112             throws UsbDescriptorsStreamFormatException {
113         stream.resetReadCount();
114 
115         int length = stream.getUnsignedByte();
116         byte type = stream.getByte();
117 
118         UsbDescriptor descriptor = null;
119         switch (type) {
120             /*
121              * Standard
122              */
123             case UsbDescriptor.DESCRIPTORTYPE_DEVICE:
124                 descriptor = mDeviceDescriptor = new UsbDeviceDescriptor(length, type);
125                 break;
126 
127             case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
128                 descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type);
129                 if (mDeviceDescriptor != null) {
130                     mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor);
131                 } else {
132                     Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!");
133                     throw new UsbDescriptorsStreamFormatException(
134                             "Config Descriptor found with no associated Device Descriptor!");
135                 }
136                 break;
137 
138             case UsbDescriptor.DESCRIPTORTYPE_INTERFACE:
139                 descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type);
140                 if (mCurConfigDescriptor != null) {
141                     mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor);
142                 } else {
143                     Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!");
144                     throw new UsbDescriptorsStreamFormatException(
145                             "Interface Descriptor found with no associated Config Descriptor!");
146                 }
147                 break;
148 
149             case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
150                 descriptor = new UsbEndpointDescriptor(length, type);
151                 if (mCurInterfaceDescriptor != null) {
152                     mCurInterfaceDescriptor.addEndpointDescriptor(
153                             (UsbEndpointDescriptor) descriptor);
154                 } else {
155                     Log.e(TAG,
156                             "Endpoint Descriptor found with no associated Interface Descriptor!");
157                     throw new UsbDescriptorsStreamFormatException(
158                             "Endpoint Descriptor found with no associated Interface Descriptor!");
159                 }
160                 break;
161 
162             /*
163              * HID
164              */
165             case UsbDescriptor.DESCRIPTORTYPE_HID:
166                 descriptor = new UsbHIDDescriptor(length, type);
167                 break;
168 
169             /*
170              * Other
171              */
172             case UsbDescriptor.DESCRIPTORTYPE_INTERFACEASSOC:
173                 descriptor = new UsbInterfaceAssoc(length, type);
174                 break;
175 
176             /*
177              * Audio Class Specific
178              */
179             case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE:
180                 descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
181                 break;
182 
183             case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT:
184                 descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
185                 break;
186 
187             default:
188                 break;
189         }
190 
191         if (descriptor == null) {
192             // Unknown Descriptor
193             Log.i(TAG, "Unknown Descriptor len: " + length + " type:0x"
194                     + Integer.toHexString(type));
195             descriptor = new UsbUnknown(length, type);
196         }
197 
198         return descriptor;
199     }
200 
getDeviceDescriptor()201     public UsbDeviceDescriptor getDeviceDescriptor() {
202         return mDeviceDescriptor;
203     }
204 
getCurInterface()205     public UsbInterfaceDescriptor getCurInterface() {
206         return mCurInterfaceDescriptor;
207     }
208 
209     /**
210      * @hide
211      */
parseDescriptors(byte[] descriptors)212     public void parseDescriptors(byte[] descriptors) {
213         if (DEBUG) {
214             Log.d(TAG, "parseDescriptors() - start");
215         }
216 
217         ByteStream stream = new ByteStream(descriptors);
218         while (stream.available() > 0) {
219             UsbDescriptor descriptor = null;
220             try {
221                 descriptor = allocDescriptor(stream);
222             } catch (Exception ex) {
223                 Log.e(TAG, "Exception allocating USB descriptor.", ex);
224             }
225 
226             if (descriptor != null) {
227                 // Parse
228                 try {
229                     descriptor.parseRawDescriptors(stream);
230 
231                     // Clean up
232                     descriptor.postParse(stream);
233                 } catch (Exception ex) {
234                     Log.e(TAG, "Exception parsing USB descriptors.", ex);
235 
236                     // Clean up
237                     descriptor.setStatus(UsbDescriptor.STATUS_PARSE_EXCEPTION);
238                 } finally {
239                     mDescriptors.add(descriptor);
240                 }
241             }
242         }
243         if (DEBUG) {
244             Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors.");
245         }
246     }
247 
getRawDescriptors()248     public byte[] getRawDescriptors() {
249         return getRawDescriptors_native(mDeviceAddr);
250     }
251 
getRawDescriptors_native(String deviceAddr)252     private native byte[] getRawDescriptors_native(String deviceAddr);
253 
254     /**
255      * @hide
256      */
getDescriptorString(int stringId)257     public String getDescriptorString(int stringId) {
258         return getDescriptorString_native(mDeviceAddr, stringId);
259     }
260 
getDescriptorString_native(String deviceAddr, int stringId)261     private native String getDescriptorString_native(String deviceAddr, int stringId);
262 
getParsingSpec()263     public int getParsingSpec() {
264         return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
265     }
266 
getDescriptors()267     public ArrayList<UsbDescriptor> getDescriptors() {
268         return mDescriptors;
269     }
270 
271     /**
272      * @hide
273      */
toAndroidUsbDevice()274     public UsbDevice.Builder toAndroidUsbDevice() {
275         if (mDeviceDescriptor == null) {
276             Log.e(TAG, "toAndroidUsbDevice() ERROR - No Device Descriptor");
277             return null;
278         }
279 
280         UsbDevice.Builder device = mDeviceDescriptor.toAndroid(this);
281         if (device == null) {
282             Log.e(TAG, "toAndroidUsbDevice() ERROR Creating Device");
283         }
284         return device;
285     }
286 
287     /**
288      * @hide
289      */
getDescriptors(byte type)290     public ArrayList<UsbDescriptor> getDescriptors(byte type) {
291         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
292         for (UsbDescriptor descriptor : mDescriptors) {
293             if (descriptor.getType() == type) {
294                 list.add(descriptor);
295             }
296         }
297         return list;
298     }
299 
300     /**
301      * @hide
302      */
getInterfaceDescriptorsForClass(int usbClass)303     public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) {
304         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
305         for (UsbDescriptor descriptor : mDescriptors) {
306             // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE
307             if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_INTERFACE) {
308                 if (descriptor instanceof UsbInterfaceDescriptor) {
309                     UsbInterfaceDescriptor intrDesc = (UsbInterfaceDescriptor) descriptor;
310                     if (intrDesc.getUsbClass() == usbClass) {
311                         list.add(descriptor);
312                     }
313                 } else {
314                     Log.w(TAG, "Unrecognized Interface l: " + descriptor.getLength()
315                             + " t:0x" + Integer.toHexString(descriptor.getType()));
316                 }
317             }
318         }
319         return list;
320     }
321 
322     /**
323      * @hide
324      */
getACInterfaceDescriptors(byte subtype, int subclass)325     public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) {
326         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
327         for (UsbDescriptor descriptor : mDescriptors) {
328             if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) {
329                 // ensure that this isn't an unrecognized DESCRIPTORTYPE_AUDIO_INTERFACE
330                 if (descriptor instanceof UsbACInterface) {
331                     UsbACInterface acDescriptor = (UsbACInterface) descriptor;
332                     if (acDescriptor.getSubtype() == subtype
333                             && acDescriptor.getSubclass() == subclass) {
334                         list.add(descriptor);
335                     }
336                 } else {
337                     Log.w(TAG, "Unrecognized Audio Interface l: " + descriptor.getLength()
338                             + " t:0x" + Integer.toHexString(descriptor.getType()));
339                 }
340             }
341         }
342         return list;
343     }
344 
345     /*
346      * Attribute predicates
347      */
348     /**
349      * @hide
350      */
hasInput()351     public boolean hasInput() {
352         if (DEBUG) {
353             Log.d(TAG, "---- hasInput()");
354         }
355         ArrayList<UsbDescriptor> acDescriptors =
356                 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
357                 UsbACInterface.AUDIO_AUDIOCONTROL);
358         boolean hasInput = false;
359         for (UsbDescriptor descriptor : acDescriptors) {
360             if (descriptor instanceof UsbACTerminal) {
361                 UsbACTerminal inDescr = (UsbACTerminal) descriptor;
362                 // Check for input and bi-directional terminal types
363                 int type = inDescr.getTerminalType();
364                 if (DEBUG) {
365                     Log.d(TAG, "  type:0x" + Integer.toHexString(type));
366                 }
367                 int terminalCategory = type & ~0xFF;
368                 if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED
369                         && terminalCategory != UsbTerminalTypes.TERMINAL_OUT_UNDEFINED) {
370                     // If not explicitly a USB connection or output, it could be an input.
371                     hasInput = true;
372                     break;
373                 }
374             } else {
375                 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength()
376                         + " t:0x" + Integer.toHexString(descriptor.getType()));
377             }
378         }
379 
380         if (DEBUG) {
381             Log.d(TAG, "hasInput() = " + hasInput);
382         }
383         return hasInput;
384     }
385 
386     /**
387      * @hide
388      */
hasOutput()389     public boolean hasOutput() {
390         if (DEBUG) {
391             Log.d(TAG, "---- hasOutput()");
392         }
393         ArrayList<UsbDescriptor> acDescriptors =
394                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
395                 UsbACInterface.AUDIO_AUDIOCONTROL);
396         boolean hasOutput = false;
397         for (UsbDescriptor descriptor : acDescriptors) {
398             if (descriptor instanceof UsbACTerminal) {
399                 UsbACTerminal outDescr = (UsbACTerminal) descriptor;
400                 // Check for output and bi-directional terminal types
401                 int type = outDescr.getTerminalType();
402                 if (DEBUG) {
403                     Log.d(TAG, "  type:0x" + Integer.toHexString(type));
404                 }
405                 int terminalCategory = type & ~0xFF;
406                 if (terminalCategory != UsbTerminalTypes.TERMINAL_USB_UNDEFINED
407                         && terminalCategory != UsbTerminalTypes.TERMINAL_IN_UNDEFINED) {
408                     // If not explicitly a USB connection or input, it could be an output.
409                     hasOutput = true;
410                     break;
411                 }
412             } else {
413                 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength()
414                         + " t:0x" + Integer.toHexString(descriptor.getType()));
415             }
416         }
417         if (DEBUG) {
418             Log.d(TAG, "hasOutput() = " + hasOutput);
419         }
420         return hasOutput;
421     }
422 
423     /**
424      * @hide
425      */
hasMic()426     public boolean hasMic() {
427         boolean hasMic = false;
428 
429         ArrayList<UsbDescriptor> acDescriptors =
430                 getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
431                 UsbACInterface.AUDIO_AUDIOCONTROL);
432         for (UsbDescriptor descriptor : acDescriptors) {
433             if (descriptor instanceof UsbACTerminal) {
434                 UsbACTerminal inDescr = (UsbACTerminal) descriptor;
435                 if (inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_IN_MIC
436                         || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET
437                         || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED
438                         || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_LINE) {
439                     hasMic = true;
440                     break;
441                 }
442             } else {
443                 Log.w(TAG, "Undefined Audio Input terminal l: " + descriptor.getLength()
444                         + " t:0x" + Integer.toHexString(descriptor.getType()));
445             }
446         }
447         return hasMic;
448     }
449 
450     /**
451      * @hide
452      */
hasSpeaker()453     public boolean hasSpeaker() {
454         boolean hasSpeaker = false;
455 
456         ArrayList<UsbDescriptor> acDescriptors =
457                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
458                         UsbACInterface.AUDIO_AUDIOCONTROL);
459         for (UsbDescriptor descriptor : acDescriptors) {
460             if (descriptor instanceof UsbACTerminal) {
461                 UsbACTerminal outDescr = (UsbACTerminal) descriptor;
462                 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER
463                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
464                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
465                     hasSpeaker = true;
466                     break;
467                 }
468             } else {
469                 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength()
470                         + " t:0x" + Integer.toHexString(descriptor.getType()));
471             }
472         }
473 
474         return hasSpeaker;
475     }
476 
477     /**
478      *@ hide
479      */
hasAudioInterface()480     public boolean hasAudioInterface() {
481         ArrayList<UsbDescriptor> descriptors =
482                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
483         return !descriptors.isEmpty();
484     }
485 
486     /**
487      * @hide
488      */
hasHIDInterface()489     public boolean hasHIDInterface() {
490         ArrayList<UsbDescriptor> descriptors =
491                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID);
492         return !descriptors.isEmpty();
493     }
494 
495     /**
496      * @hide
497      */
hasStorageInterface()498     public boolean hasStorageInterface() {
499         ArrayList<UsbDescriptor> descriptors =
500                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_STORAGE);
501         return !descriptors.isEmpty();
502     }
503 
504     /**
505      * @hide
506      */
hasMIDIInterface()507     public boolean hasMIDIInterface() {
508         ArrayList<UsbDescriptor> descriptors =
509                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
510         for (UsbDescriptor descriptor : descriptors) {
511             // enusure that this isn't an unrecognized interface descriptor
512             if (descriptor instanceof UsbInterfaceDescriptor) {
513                 UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor;
514                 if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
515                     return true;
516                 }
517             } else {
518                 Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
519                         + " t:0x" + Integer.toHexString(descriptor.getType()));
520             }
521         }
522         return false;
523     }
524 
525     /**
526      * @hide
527      */
getInputHeadsetProbability()528     public float getInputHeadsetProbability() {
529         if (hasMIDIInterface()) {
530             return 0.0f;
531         }
532 
533         float probability = 0.0f;
534 
535         // Look for a microphone
536         boolean hasMic = hasMic();
537 
538         // Look for a "speaker"
539         boolean hasSpeaker = hasSpeaker();
540 
541         if (hasMic && hasSpeaker) {
542             probability += 0.75f;
543         }
544 
545         if (hasMic && hasHIDInterface()) {
546             probability += 0.25f;
547         }
548 
549         return probability;
550     }
551 
552     /**
553      * getInputHeadsetProbability() reports a probability of a USB Input peripheral being a
554      * headset. The probability range is between 0.0f (definitely NOT a headset) and
555      * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
556      * to count on the peripheral being a headset.
557      */
isInputHeadset()558     public boolean isInputHeadset() {
559         return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
560     }
561 
562     /**
563      * @hide
564      */
getOutputHeadsetProbability()565     public float getOutputHeadsetProbability() {
566         if (hasMIDIInterface()) {
567             return 0.0f;
568         }
569 
570         float probability = 0.0f;
571         ArrayList<UsbDescriptor> acDescriptors;
572 
573         // Look for a "speaker"
574         boolean hasSpeaker = false;
575         acDescriptors =
576                 getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
577                         UsbACInterface.AUDIO_AUDIOCONTROL);
578         for (UsbDescriptor descriptor : acDescriptors) {
579             if (descriptor instanceof UsbACTerminal) {
580                 UsbACTerminal outDescr = (UsbACTerminal) descriptor;
581                 if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER
582                         || outDescr.getTerminalType()
583                             == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
584                         || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
585                     hasSpeaker = true;
586                     break;
587                 }
588             } else {
589                 Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength()
590                         + " t:0x" + Integer.toHexString(descriptor.getType()));
591             }
592         }
593 
594         if (hasSpeaker) {
595             probability += 0.75f;
596         }
597 
598         if (hasSpeaker && hasHIDInterface()) {
599             probability += 0.25f;
600         }
601 
602         return probability;
603     }
604 
605     /**
606      * getOutputHeadsetProbability() reports a probability of a USB Output peripheral being a
607      * headset. The probability range is between 0.0f (definitely NOT a headset) and
608      * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
609      * to count on the peripheral being a headset.
610      */
isOutputHeadset()611     public boolean isOutputHeadset() {
612         return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
613     }
614 
615 }
616