Home | History | Annotate | Download | only in descriptors
      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      */
     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      */
     61     public UsbDescriptorParser(String deviceAddr, byte[] rawDescriptors) {
     62         mDeviceAddr = deviceAddr;
     63         mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE);
     64         parseDescriptors(rawDescriptors);
     65     }
     66 
     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      */
     77     public int getUsbSpec() {
     78         if (mDeviceDescriptor != null) {
     79             return mDeviceDescriptor.getSpec();
     80         } else {
     81             throw new IllegalArgumentException();
     82         }
     83     }
     84 
     85     public void setACInterfaceSpec(int spec) {
     86         mACInterfacesSpec = spec;
     87     }
     88 
     89     public int getACInterfaceSpec() {
     90         return mACInterfacesSpec;
     91     }
     92 
     93     private class UsbDescriptorsStreamFormatException extends Exception {
     94         String mMessage;
     95         UsbDescriptorsStreamFormatException(String message) {
     96             mMessage = message;
     97         }
     98 
     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 
    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 
    201     public UsbDeviceDescriptor getDeviceDescriptor() {
    202         return mDeviceDescriptor;
    203     }
    204 
    205     public UsbInterfaceDescriptor getCurInterface() {
    206         return mCurInterfaceDescriptor;
    207     }
    208 
    209     /**
    210      * @hide
    211      */
    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 
    248     public byte[] getRawDescriptors() {
    249         return getRawDescriptors_native(mDeviceAddr);
    250     }
    251 
    252     private native byte[] getRawDescriptors_native(String deviceAddr);
    253 
    254     /**
    255      * @hide
    256      */
    257     public String getDescriptorString(int stringId) {
    258         return getDescriptorString_native(mDeviceAddr, stringId);
    259     }
    260 
    261     private native String getDescriptorString_native(String deviceAddr, int stringId);
    262 
    263     public int getParsingSpec() {
    264         return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
    265     }
    266 
    267     public ArrayList<UsbDescriptor> getDescriptors() {
    268         return mDescriptors;
    269     }
    270 
    271     /**
    272      * @hide
    273      */
    274     public UsbDevice toAndroidUsbDevice() {
    275         if (mDeviceDescriptor == null) {
    276             Log.e(TAG, "toAndroidUsbDevice() ERROR - No Device Descriptor");
    277             return null;
    278         }
    279 
    280         UsbDevice 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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    480     public boolean hasAudioInterface() {
    481         ArrayList<UsbDescriptor> descriptors =
    482                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
    483         return !descriptors.isEmpty();
    484     }
    485 
    486     /**
    487      * @hide
    488      */
    489     public boolean hasHIDInterface() {
    490         ArrayList<UsbDescriptor> descriptors =
    491                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID);
    492         return !descriptors.isEmpty();
    493     }
    494 
    495     /**
    496      * @hide
    497      */
    498     public boolean hasStorageInterface() {
    499         ArrayList<UsbDescriptor> descriptors =
    500                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_STORAGE);
    501         return !descriptors.isEmpty();
    502     }
    503 
    504     /**
    505      * @hide
    506      */
    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      */
    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      */
    558     public boolean isInputHeadset() {
    559         return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
    560     }
    561 
    562     /**
    563      * @hide
    564      */
    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      */
    611     public boolean isOutputHeadset() {
    612         return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
    613     }
    614 
    615 }
    616