Merge "Adding USB Headset awareness." into oc-dr1-dev
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index a3d28bb..e2a82b7 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -33,6 +33,7 @@
     $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
     $(LOCAL_REL_DIR)/com_android_server_vr_VrManagerService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
+    $(LOCAL_REL_DIR)/com_android_server_UsbDescriptorParser.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
     $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
new file mode 100644
index 0000000..98c5ec1
--- /dev/null
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "UsbHostManagerJNI"
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include <usbhost/usbhost.h>
+
+#define MAX_DESCRIPTORS_LENGTH 16384
+
+// com.android.server.usb.descriptors
+extern "C" {
+jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors(
+        JNIEnv* env, jobject thiz, jstring deviceAddr) {
+    const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL);
+    struct usb_device* device = usb_device_open(deviceAddrStr);
+    env->ReleaseStringUTFChars(deviceAddr, deviceAddrStr);
+
+    if (!device) {
+        ALOGE("usb_device_open failed");
+        return NULL;
+    }
+
+    int fd = usb_device_get_fd(device);
+    if (fd < 0) {
+        return NULL;
+    }
+
+    // from android_hardware_UsbDeviceConnection_get_desc()
+    jbyte buffer[MAX_DESCRIPTORS_LENGTH];
+    lseek(fd, 0, SEEK_SET);
+    int numBytes = read(fd, buffer, sizeof(buffer));
+
+    usb_device_close(device);
+
+    jbyteArray ret = NULL;
+    if (numBytes != 0) {
+        ret = env->NewByteArray(numBytes);
+        env->SetByteArrayRegion(ret, 0, numBytes, buffer);
+    }
+    return ret;
+}
+
+} // extern "C"
+
+
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index d315b18..68c1d5f 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -65,6 +65,9 @@
     private final HashMap<UsbDevice,UsbAudioDevice>
         mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
 
+    private boolean mIsInputHeadset; // as reported by UsbDescriptorParser
+    private boolean mIsOutputHeadset; // as reported by UsbDescriptorParser
+
     private final HashMap<UsbDevice,UsbMidiDevice>
         mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>();
 
@@ -184,9 +187,14 @@
         try {
             // Playback Device
             if (audioDevice.mHasPlayback) {
-                int device = (audioDevice == mAccessoryAudioDevice ?
-                        AudioSystem.DEVICE_OUT_USB_ACCESSORY :
-                        AudioSystem.DEVICE_OUT_USB_DEVICE);
+                int device;
+                if (mIsOutputHeadset) {
+                    device = AudioSystem.DEVICE_OUT_USB_HEADSET;
+                } else {
+                    device = (audioDevice == mAccessoryAudioDevice
+                        ? AudioSystem.DEVICE_OUT_USB_ACCESSORY
+                        : AudioSystem.DEVICE_OUT_USB_DEVICE);
+                }
                 if (DEBUG) {
                     Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) +
                             " addr:" + address + " name:" + audioDevice.getDeviceName());
@@ -197,9 +205,14 @@
 
             // Capture Device
             if (audioDevice.mHasCapture) {
-               int device = (audioDevice == mAccessoryAudioDevice ?
-                        AudioSystem.DEVICE_IN_USB_ACCESSORY :
-                        AudioSystem.DEVICE_IN_USB_DEVICE);
+                int device;
+                if (mIsInputHeadset) {
+                    device = AudioSystem.DEVICE_IN_USB_HEADSET;
+                } else {
+                    device = (audioDevice == mAccessoryAudioDevice
+                        ? AudioSystem.DEVICE_IN_USB_ACCESSORY
+                        : AudioSystem.DEVICE_IN_USB_DEVICE);
+                }
                 mAudioService.setWiredDeviceConnectionState(
                         device, state, address, audioDevice.getDeviceName(), TAG);
             }
@@ -343,12 +356,16 @@
         return selectAudioCard(mCardsParser.getDefaultCard());
     }
 
-    /* package */ void usbDeviceAdded(UsbDevice usbDevice) {
-       if (DEBUG) {
-          Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() +
-                  " nm:" + usbDevice.getProductName());
+    /* package */ void usbDeviceAdded(UsbDevice usbDevice,
+            boolean isInputHeadset, boolean isOutputHeadset) {
+        if (DEBUG) {
+            Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName()
+                    + " nm:" + usbDevice.getProductName());
         }
 
+        mIsInputHeadset = isInputHeadset;
+        mIsOutputHeadset = isOutputHeadset;
+
         // Is there an audio interface in there?
         boolean isAudioDevice = false;
 
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index af78c05..40aadbb 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -16,7 +16,6 @@
 
 package com.android.server.usb;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
@@ -32,6 +31,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -259,7 +259,14 @@
                     getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice,
                             usbDeviceConnectionHandler);
                 }
-                mUsbAlsaManager.usbDeviceAdded(mNewDevice);
+                // deviceName is something like: "/dev/bus/usb/001/001"
+                UsbDescriptorParser parser = new UsbDescriptorParser();
+                if (parser.parseDevice(mNewDevice.getDeviceName())) {
+                    Slog.i(TAG, "---- isHeadset[in:" + parser.isInputHeadset()
+                            + " , out:" + parser.isOutputHeadset() + "]");
+                    mUsbAlsaManager.usbDeviceAdded(mNewDevice,
+                            parser.isInputHeadset(), parser.isOutputHeadset());
+                }
             } else {
                 Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
             }
diff --git a/services/usb/java/com/android/server/usb/descriptors/ByteStream.java b/services/usb/java/com/android/server/usb/descriptors/ByteStream.java
new file mode 100644
index 0000000..d678931
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/ByteStream.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.annotation.NonNull;
+
+/**
+ * @hide
+ * A stream interface wrapping a byte array. Very much like a java.io.ByteArrayInputStream
+ * but with the capability to "back up" in situations where the parser discovers that a
+ * UsbDescriptor has overrun its length.
+ */
+public class ByteStream {
+    private static final String TAG = "ByteStream";
+
+    /** The byte array being wrapped */
+    @NonNull
+    private final byte[] mBytes; // this is never null.
+
+    /**
+     * The index into the byte array to be read next.
+     * This value is altered by reading data out of the stream
+     * (using either the getByte() or unpack*() methods), or alternatively
+     * by explicitly offseting the stream position with either
+     * advance() or reverse().
+     */
+    private int mIndex;
+
+    /*
+     * This member used with resetReadCount() & getReadCount() can be used to determine how many
+     * bytes a UsbDescriptor subclass ACTUALLY reads (as opposed to what its length field says).
+     * using this info, the parser can mark a descriptor as valid or invalid and correct the stream
+     * position with advance() & reverse() to keep from "getting lost" in the descriptor stream.
+     */
+    private int mReadCount;
+
+    /**
+     * Create a ByteStream object wrapping the specified byte array.
+     *
+     * @param bytes The byte array containing the raw descriptor information retrieved from
+     *              the USB device.
+     * @throws IllegalArgumentException
+     */
+    public ByteStream(@NonNull byte[] bytes) {
+        if (bytes == null) {
+            throw new IllegalArgumentException();
+        }
+        mBytes = bytes;
+    }
+
+    /**
+     * Resets the running count of bytes read so that later we can see how much more has been read.
+     */
+    public void resetReadCount() {
+        mReadCount = 0;
+    }
+
+    /**
+     * Retrieves the running count of bytes read from the stream.
+     */
+    public int getReadCount() {
+        return mReadCount;
+    }
+
+    /**
+     * @return The value of the next byte in the stream without advancing the stream.
+     * Does not affect the running count as the byte hasn't been "consumed".
+     * @throws IndexOutOfBoundsException
+     */
+    public byte peekByte() {
+        if (available() > 0) {
+            return mBytes[mIndex + 1];
+        } else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * @return the next byte from the stream and advances the stream and the read count. Note
+     * that this is a signed byte (as is the case of byte in Java). The user may need to understand
+     * from context if it should be interpreted as an unsigned value.
+     * @throws IndexOutOfBoundsException
+     */
+    public byte getByte() {
+        if (available() > 0) {
+            mReadCount++;
+            return mBytes[mIndex++];
+        } else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * Reads 2 bytes in *little endian format* from the stream and composes a 16-bit integer.
+     * As we are storing the 2-byte value in a 4-byte integer, the upper 2 bytes are always
+     * 0, essentially making the returned value *unsigned*.
+     * @return The 16-bit integer (packed into the lower 2 bytes of an int) encoded by the
+     * next 2 bytes in the stream.
+     * @throws IndexOutOfBoundsException
+     */
+    public int unpackUsbWord() {
+        if (available() >= 2) {
+            int b0 = getByte();
+            int b1 = getByte();
+            return ((b1 << 8) & 0x0000FF00) | (b0 & 0x000000FF);
+        } else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * Reads 3 bytes in *little endian format* from the stream and composes a 24-bit integer.
+     * As we are storing the 3-byte value in a 4-byte integer, the upper byte is always
+     * 0, essentially making the returned value *unsigned*.
+     * @return The 24-bit integer (packed into the lower 3 bytes of an int) encoded by the
+     * next 3 bytes in the stream.
+     * @throws IndexOutOfBoundsException
+     */
+    public int unpackUsbTriple() {
+        if (available() >= 3) {
+            int b0 = getByte();
+            int b1 = getByte();
+            int b2 = getByte();
+            return ((b2 << 16) & 0x00FF0000) | ((b1 << 8) & 0x0000FF00) | (b0 & 0x000000FF);
+        } else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * Advances the logical position in the stream. Affects the running count also.
+     * @param numBytes The number of bytes to advance.
+     * @throws IndexOutOfBoundsException
+     * @throws IllegalArgumentException
+     */
+    public void advance(int numBytes) {
+        if (numBytes < 0) {
+            // Positive offsets only
+            throw new IllegalArgumentException();
+        }
+        // do arithmetic and comparison in long to ovoid potention integer overflow
+        long longNewIndex = (long) mIndex + (long) numBytes;
+        if (longNewIndex < (long) mBytes.length) {
+            mReadCount += numBytes;
+            mIndex += numBytes;
+        } else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * Reverse the logical position in the stream. Affects the running count also.
+     * @param numBytes The (positive) number of bytes to reverse.
+     * @throws IndexOutOfBoundsException
+     * @throws IllegalArgumentException
+     */
+    public void reverse(int numBytes) {
+        if (numBytes < 0) {
+            // Positive (reverse) offsets only
+            throw new IllegalArgumentException();
+        }
+        if (mIndex >= numBytes) {
+            mReadCount -= numBytes;
+            mIndex -= numBytes;
+        } else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    /**
+     * @return The number of bytes available to be read in the stream.
+     */
+    public int available() {
+        return mBytes.length - mIndex;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
new file mode 100644
index 0000000..96fcc6a
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Audio Control Endpoint.
+ * audio10.pdf section 4.4.2.1
+ */
+public class UsbACAudioControlEndpoint extends UsbACEndpoint {
+    private static final String TAG = "ACAudioControlEndpoint";
+
+    private byte mAddress;  // 2:1 The address of the endpoint on the USB device.
+                            // D7: Direction. 1 = IN endpoint
+                            // D6..4: Reserved, reset to zero
+                            // D3..0: The endpoint number.
+    private byte mAttribs;  // 3:1 (see ATTRIBSMASK_* below
+    private int mMaxPacketSize; // 4:2 Maximum packet size this endpoint is capable of sending
+                                // or receiving when this configuration is selected.
+    private byte mInterval; // 6:1
+
+    static final byte ADDRESSMASK_DIRECTION = (byte) 0x80;
+    static final byte ADDRESSMASK_ENDPOINT  = 0x0F;
+
+    static final byte ATTRIBSMASK_SYNC  = 0x0C;
+    static final byte ATTRIBMASK_TRANS  = 0x03;
+
+    public UsbACAudioControlEndpoint(int length, byte type, byte subclass) {
+        super(length, type, subclass);
+    }
+
+    public byte getAddress() {
+        return mAddress;
+    }
+
+    public byte getAttribs() {
+        return mAttribs;
+    }
+
+    public int getMaxPacketSize() {
+        return mMaxPacketSize;
+    }
+
+    public byte getInterval() {
+        return mInterval;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        super.parseRawDescriptors(stream);
+
+        mAddress = stream.getByte();
+        mAttribs = stream.getByte();
+        mMaxPacketSize = stream.unpackUsbWord();
+        mInterval = stream.getByte();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
new file mode 100644
index 0000000..d387883
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Streaming Endpoint
+ * see audio10.pdf section 3.7.2
+ */
+public class UsbACAudioStreamEndpoint extends UsbACEndpoint {
+    private static final String TAG = "ACAudioStreamEndpoint";
+
+    //TODO data fields...
+    public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) {
+        super(length, type, subclass);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        super.parseRawDescriptors(stream);
+
+        //TODO Read fields
+        stream.advance(mLength - stream.getReadCount());
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
new file mode 100644
index 0000000..223496ab
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.util.Log;
+
+/**
+ * @hide
+ * An audio class-specific Endpoint
+ * see audio10.pdf section 4.4.1.2
+ */
+abstract class UsbACEndpoint extends UsbDescriptor {
+    private static final String TAG = "ACEndpoint";
+
+    protected final byte mSubclass; // from the mSubclass member of the "enclosing"
+                                    // Interface Descriptor, not the stream.
+    protected byte mSubtype;        // 2:1 HEADER descriptor subtype
+
+    UsbACEndpoint(int length, byte type, byte subclass) {
+        super(length, type);
+        mSubclass = subclass;
+    }
+
+    public byte getSubclass() {
+        return mSubclass;
+    }
+
+    public byte getSubtype() {
+        return mSubtype;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mSubtype = stream.getByte();
+
+        return mLength;
+    }
+
+    public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
+            int length, byte type) {
+        UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
+        byte subClass = interfaceDesc.getUsbSubclass();
+        switch (subClass) {
+            case AUDIO_AUDIOCONTROL:
+                return new UsbACAudioControlEndpoint(length, type, subClass);
+
+            case AUDIO_AUDIOSTREAMING:
+                return new UsbACAudioStreamEndpoint(length, type, subClass);
+
+            case AUDIO_MIDISTREAMING:
+                return new UsbACMidiEndpoint(length, type, subClass);
+
+            default:
+                Log.w(TAG, "Unknown Audio Class Endpoint id:0x" + Integer.toHexString(subClass));
+                return null;
+        }
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java
new file mode 100644
index 0000000..739fe55
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Feature Unit Interface
+ * see audio10.pdf section 3.5.5
+ */
+public class UsbACFeatureUnit extends UsbACInterface {
+    private static final String TAG = "ACFeatureUnit";
+
+    // audio10.pdf section 4.3.2.5
+    public static final int CONTROL_MASK_MUTE =    0x0001;
+    public static final int CONTROL_MASK_VOL =     0x0002;
+    public static final int CONTROL_MASK_BASS =    0x0004;
+    public static final int CONTROL_MASK_MID =     0x0008;
+    public static final int CONTROL_MASK_TREB =    0x0010;
+    public static final int CONTROL_MASK_EQ =      0x0020;
+    public static final int CONTROL_MASK_AGC =     0x0040;
+    public static final int CONTROL_MASK_DELAY =   0x0080;
+    public static final int CONTROL_MASK_BOOST =   0x0100; // BASS boost
+    public static final int CONTROL_MASK_LOUD =    0x0200; // LOUDNESS
+
+    private int mNumChannels;
+
+    private byte mUnitID;   // 3:1 Constant uniquely identifying the Unit within the audio function.
+                            // This value is used in all requests to address this Unit
+    private byte mSourceID; // 4:1 ID of the Unit or Terminal to which this Feature Unit
+                            // is connected.
+    private byte mControlSize;  // 5:1 Size in bytes of an element of the mControls array: n
+    private int[] mControls;    // 6:? bitmask (see above) of supported controls in a given
+                                // logical channel
+    private byte mUnitName;     // ?:1 Index of a string descriptor, describing this Feature Unit.
+
+    public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) {
+        super(length, type, subtype, subClass);
+    }
+
+    public int getNumChannels() {
+        return mNumChannels;
+    }
+
+    public byte getUnitID() {
+        return mUnitID;
+    }
+
+    public byte getSourceID() {
+        return mSourceID;
+    }
+
+    public byte getControlSize() {
+        return mControlSize;
+    }
+
+    public int[] getControls() {
+        return mControls;
+    }
+
+    public byte getUnitName() {
+        return mUnitName;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbACHeader.java
new file mode 100644
index 0000000..e31438c
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACHeader.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Interface Header.
+ * see audio10.pdf section 4.3.2
+ */
+public class UsbACHeader extends UsbACInterface {
+    private static final String TAG = "ACHeader";
+
+    private int mADCRelease;    // 3:2 Audio Device Class Specification Release (BCD).
+    private int mTotalLength;   // 5:2 Total number of bytes returned for the class-specific
+                                // AudioControl interface descriptor. Includes the combined length
+                                // of this descriptor header and all Unit and Terminal descriptors.
+    private byte mNumInterfaces = 0; // 7:1 The number of AudioStreaming and MIDIStreaming
+                                     // interfaces in the Audio Interface Collection to which this
+                                     // AudioControl interface belongs: n
+    private byte[] mInterfaceNums = null;   // 8:n List of Audio/MIDI streaming interface
+                                            // numbers associate with this endpoint
+    private byte mControls;                 // Vers 2.0 thing
+
+    public UsbACHeader(int length, byte type, byte subtype, byte subclass) {
+        super(length, type, subtype, subclass);
+    }
+
+    public int getADCRelease() {
+        return mADCRelease;
+    }
+
+    public int getTotalLength() {
+        return mTotalLength;
+    }
+
+    public byte getNumInterfaces() {
+        return mNumInterfaces;
+    }
+
+    public byte[] getInterfaceNums() {
+        return mInterfaceNums;
+    }
+
+    public byte getControls() {
+        return mControls;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mADCRelease = stream.unpackUsbWord();
+
+        mTotalLength = stream.unpackUsbWord();
+        if (mADCRelease >= 0x200) {
+            mControls = stream.getByte();
+        } else {
+            mNumInterfaces = stream.getByte();
+            mInterfaceNums = new byte[mNumInterfaces];
+            for (int index = 0; index < mNumInterfaces; index++) {
+                mInterfaceNums[index] = stream.getByte();
+            }
+        }
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInputTerminal.java
new file mode 100644
index 0000000..653a7de
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInputTerminal.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Input Terminal interface.
+ * see audio10.pdf section 4.3.2.1
+ */
+public class UsbACInputTerminal extends UsbACTerminal {
+    private static final String TAG = "ACInputTerminal";
+
+    private byte mNrChannels;       // 7:1 1 Channel (0x01)
+                                    // Number of logical output channels in the
+                                    // Terminal’s output audio channel cluster
+    private int mChannelConfig;     // 8:2 Mono (0x0000)
+    private byte mChannelNames;     // 10:1 Unused (0x00)
+    private byte mTerminal;         // 11:1 Unused (0x00)
+
+    public UsbACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+        super(length, type, subtype, subclass);
+    }
+
+    public byte getNrChannels() {
+        return mNrChannels;
+    }
+
+    public int getChannelConfig() {
+        return mChannelConfig;
+    }
+
+    public byte getChannelNames() {
+        return mChannelNames;
+    }
+
+    public byte getTerminal() {
+        return mTerminal;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        super.parseRawDescriptors(stream);
+
+        mNrChannels = stream.getByte();
+        mChannelConfig = stream.unpackUsbWord();
+        mChannelNames = stream.getByte();
+        mTerminal = stream.getByte();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java
new file mode 100644
index 0000000..0ab7fcc
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.util.Log;
+
+/**
+ * @hide
+ * An audio class-specific Interface.
+ * see audio10.pdf section 4.3.2
+ */
+public abstract class UsbACInterface extends UsbDescriptor {
+    private static final String TAG = "ACInterface";
+
+    // Audio Control Subtypes
+    public static final byte ACI_UNDEFINED = 0;
+    public static final byte ACI_HEADER = 1;
+    public static final byte ACI_INPUT_TERMINAL = 2;
+    public static final byte ACI_OUTPUT_TERMINAL = 3;
+    public static final byte ACI_MIXER_UNIT = 4;
+    public static final byte ACI_SELECTOR_UNIT = 5;
+    public static final byte ACI_FEATURE_UNIT = 6;
+    public static final byte ACI_PROCESSING_UNIT = 7;
+    public static final byte ACI_EXTENSION_UNIT = 8;
+
+    // Audio Streaming Subtypes
+    public static final byte ASI_UNDEFINED = 0;
+    public static final byte ASI_GENERAL = 1;
+    public static final byte ASI_FORMAT_TYPE = 2;
+    public static final byte ASI_FORMAT_SPECIFIC = 3;
+
+    // MIDI Streaming Subtypes
+    public static final byte MSI_UNDEFINED = 0;
+    public static final byte MSI_HEADER = 1;
+    public static final byte MSI_IN_JACK = 2;
+    public static final byte MSI_OUT_JACK = 3;
+    public static final byte MSI_ELEMENT = 4;
+
+    // Sample format IDs (encodings)
+    // FORMAT_I
+    public static final int FORMAT_I_UNDEFINED     = 0x0000;
+    public static final int FORMAT_I_PCM           = 0x0001;
+    public static final int FORMAT_I_PCM8          = 0x0002;
+    public static final int FORMAT_I_IEEE_FLOAT    = 0x0003;
+    public static final int FORMAT_I_ALAW          = 0x0004;
+    public static final int FORMAT_I_MULAW         = 0x0005;
+    // FORMAT_II
+    public static final int FORMAT_II_UNDEFINED    = 0x1000;
+    public static final int FORMAT_II_MPEG         = 0x1001;
+    public static final int FORMAT_II_AC3          = 0x1002;
+    // FORMAT_III
+    public static final int FORMAT_III_UNDEFINED              = 0x2000;
+    public static final int FORMAT_III_IEC1937AC3             = 0x2001;
+    public static final int FORMAT_III_IEC1937_MPEG1_Layer1   = 0x2002;
+    public static final int FORMAT_III_IEC1937_MPEG1_Layer2   = 0x2003;
+    public static final int FORMAT_III_IEC1937_MPEG2_EXT      = 0x2004;
+    public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005;
+
+    protected final byte mSubtype;  // 2:1 HEADER descriptor subtype
+    protected final byte mSubclass; // from the mSubclass member of the
+                                    // "enclosing" Interface Descriptor
+
+    public UsbACInterface(int length, byte type, byte subtype, byte subclass) {
+        super(length, type);
+        mSubtype = subtype;
+        mSubclass = subclass;
+    }
+
+    public byte getSubtype() {
+        return mSubtype;
+    }
+
+    public byte getSubclass() {
+        return mSubclass;
+    }
+
+    private static UsbDescriptor allocAudioControlDescriptor(ByteStream stream,
+            int length, byte type, byte subtype, byte subClass) {
+        switch (subtype) {
+            case ACI_HEADER:
+                return new UsbACHeader(length, type, subtype, subClass);
+
+            case ACI_INPUT_TERMINAL:
+                return new UsbACInputTerminal(length, type, subtype, subClass);
+
+            case ACI_OUTPUT_TERMINAL:
+                return new UsbACOutputTerminal(length, type, subtype, subClass);
+
+            case ACI_SELECTOR_UNIT:
+                return new UsbACSelectorUnit(length, type, subtype, subClass);
+
+            case ACI_FEATURE_UNIT:
+                return new UsbACFeatureUnit(length, type, subtype, subClass);
+
+            case ACI_MIXER_UNIT:
+                return new UsbACMixerUnit(length, type, subtype, subClass);
+
+            case ACI_PROCESSING_UNIT:
+            case ACI_EXTENSION_UNIT:
+            case ACI_UNDEFINED:
+                // break; Fall through until we implement this descriptor
+            default:
+                Log.w(TAG, "Unknown Audio Class Interface subtype:0x"
+                        + Integer.toHexString(subtype));
+                return null;
+        }
+    }
+
+    private static UsbDescriptor allocAudioStreamingDescriptor(ByteStream stream,
+            int length, byte type, byte subtype, byte subClass) {
+        switch (subtype) {
+            case ASI_GENERAL:
+                return new UsbASGeneral(length, type, subtype, subClass);
+
+            case ASI_FORMAT_TYPE:
+                return UsbASFormat.allocDescriptor(stream, length, type, subtype, subClass);
+
+            case ASI_FORMAT_SPECIFIC:
+            case ASI_UNDEFINED:
+                // break; Fall through until we implement this descriptor
+            default:
+                Log.w(TAG, "Unknown Audio Streaming Interface subtype:0x"
+                        + Integer.toHexString(subtype));
+                return null;
+        }
+    }
+
+    private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type,
+            byte subtype, byte subClass) {
+        switch (subtype) {
+            case MSI_HEADER:
+                return new UsbMSMidiHeader(length, type, subtype, subClass);
+
+            case MSI_IN_JACK:
+                return new UsbMSMidiInputJack(length, type, subtype, subClass);
+
+            case MSI_OUT_JACK:
+                return new UsbMSMidiOutputJack(length, type, subtype, subClass);
+
+            case MSI_ELEMENT:
+                // break;
+                // Fall through until we implement that descriptor
+
+            case MSI_UNDEFINED:
+                // break; Fall through until we implement this descriptor
+            default:
+                Log.w(TAG, "Unknown MIDI Streaming Interface subtype:0x"
+                        + Integer.toHexString(subtype));
+                return null;
+        }
+    }
+
+    /**
+     * Allocates an audio class interface subtype based on subtype and subclass.
+     */
+    public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, ByteStream stream,
+            int length, byte type) {
+        byte subtype = stream.getByte();
+        UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
+        byte subClass = interfaceDesc.getUsbSubclass();
+        switch (subClass) {
+            case AUDIO_AUDIOCONTROL:
+                return allocAudioControlDescriptor(stream, length, type, subtype, subClass);
+
+            case AUDIO_AUDIOSTREAMING:
+                return allocAudioStreamingDescriptor(stream, length, type, subtype, subClass);
+
+            case AUDIO_MIDISTREAMING:
+                return allocMidiStreamingDescriptor(length, type, subtype, subClass);
+
+            default:
+                Log.w(TAG, "Unknown Audio Class Interface Subclass: 0x"
+                        + Integer.toHexString(subClass));
+                return null;
+        }
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
new file mode 100644
index 0000000..9c07242
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Midi Endpoint.
+ * see midi10.pdf section 6.2.2
+ */
+public class UsbACMidiEndpoint extends UsbACEndpoint {
+    private static final String TAG = "ACMidiEndpoint";
+
+    private byte mNumJacks;
+    private byte[] mJackIds;
+
+    public UsbACMidiEndpoint(int length, byte type, byte subclass) {
+        super(length, type, subclass);
+    }
+
+    public byte getNumJacks() {
+        return mNumJacks;
+    }
+
+    public byte[] getJackIds() {
+        return mJackIds;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        super.parseRawDescriptors(stream);
+
+        mNumJacks = stream.getByte();
+        mJackIds = new byte[mNumJacks];
+        for (int jack = 0; jack < mNumJacks; jack++) {
+            mJackIds[jack] = stream.getByte();
+        }
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java
new file mode 100644
index 0000000..552b5ae
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Mixer Interface.
+ * see audio10.pdf section 4.3.2.3
+ */
+public class UsbACMixerUnit extends UsbACInterface {
+    private static final String TAG = "ACMixerUnit";
+
+    private byte mUnitID;       // 3:1
+    private byte mNumInputs;    // 4:1 Number of Input Pins of this Unit.
+    private byte[] mInputIDs;   // 5...:1 ID of the Unit or Terminal to which the Input Pins
+                                // are connected.
+    private byte mNumOutputs;   // The number of output channels
+    private int mChannelConfig; // Spacial location of output channels
+    private byte mChanNameID;   // First channel name string descriptor ID
+    private byte[] mControls;   // bitmasks of which controls are present for each channel
+    private byte mNameID;       // string descriptor ID of mixer name
+
+    public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+        super(length, type, subtype, subClass);
+    }
+
+    public byte getUnitID() {
+        return mUnitID;
+    }
+
+    public byte getNumInputs() {
+        return mNumInputs;
+    }
+
+    public byte[] getInputIDs() {
+        return mInputIDs;
+    }
+
+    public byte getNumOutputs() {
+        return mNumOutputs;
+    }
+
+    public int getChannelConfig() {
+        return mChannelConfig;
+    }
+
+    public byte getChanNameID() {
+        return mChanNameID;
+    }
+
+    public byte[] getControls() {
+        return mControls;
+    }
+
+    public byte getNameID() {
+        return mNameID;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mUnitID = stream.getByte();
+        mNumInputs = stream.getByte();
+        mInputIDs = new byte[mNumInputs];
+        for (int input = 0; input < mNumInputs; input++) {
+            mInputIDs[input] = stream.getByte();
+        }
+        mNumOutputs = stream.getByte();
+        mChannelConfig = stream.unpackUsbWord();
+        mChanNameID = stream.getByte();
+
+        int controlArraySize;
+        int totalChannels = mNumInputs * mNumOutputs;
+        if (totalChannels % 8 == 0) {
+            controlArraySize = totalChannels / 8;
+        } else {
+            controlArraySize = totalChannels / 8 + 1;
+        }
+        mControls = new byte[controlArraySize];
+        for (int index = 0; index < controlArraySize; index++) {
+            mControls[index] = stream.getByte();
+        }
+
+        mNameID = stream.getByte();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACOutputTerminal.java
new file mode 100644
index 0000000..f957e3d
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACOutputTerminal.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Output Terminal Interface.
+ * see audio10.pdf section 4.3.2.2
+ */
+public class UsbACOutputTerminal extends UsbACTerminal {
+    private static final String TAG = "ACOutputTerminal";
+
+    private byte mSourceID;         // 7:1 From Input Terminal. (0x01)
+    private byte mTerminal;         // 8:1 Unused.
+
+    public UsbACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+        super(length, type, subtype, subClass);
+    }
+
+    public byte getSourceID() {
+        return mSourceID;
+    }
+
+    public byte getTerminal() {
+        return mTerminal;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        super.parseRawDescriptors(stream);
+
+        mSourceID = stream.getByte();
+        mTerminal = stream.getByte();
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java
new file mode 100644
index 0000000..b1f60bd
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Selector Unit Interface.
+ * see audio10.pdf section 4.3.2.4
+ */
+public class UsbACSelectorUnit extends UsbACInterface {
+    private static final String TAG = "ACSelectorUnit";
+
+    private byte mUnitID;   // 3:1 Constant uniquely identifying the Unit within the audio function.
+                            // This value is used in all requests to address this Unit.
+    private byte mNumPins;  // 4:1 Number of input pins in this unit
+    private byte[] mSourceIDs;  // 5+mNumPins:1 ID of the Unit or Terminal to which the first
+                                // Input Pin of this Selector Unit is connected.
+    private byte mNameIndex;    // Index of a string descriptor, describing the Selector Unit.
+
+    public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) {
+        super(length, type, subtype, subClass);
+    }
+
+    public byte getUnitID() {
+        return mUnitID;
+    }
+
+    public byte getNumPins() {
+        return mNumPins;
+    }
+
+    public byte[] getSourceIDs() {
+        return mSourceIDs;
+    }
+
+    public byte getNameIndex() {
+        return mNameIndex;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mUnitID = stream.getByte();
+        mNumPins = stream.getByte();
+        mSourceIDs = new byte[mNumPins];
+        for (int index = 0; index < mNumPins; index++) {
+            mSourceIDs[index] = stream.getByte();
+        }
+        mNameIndex = stream.getByte();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
new file mode 100644
index 0000000..ea80208
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ */
+public abstract class UsbACTerminal extends UsbACInterface {
+    // Note that these fields are the same for both the
+    // audio class-specific Output Terminal Interface.(audio10.pdf section 4.3.2.2)
+    // and audio class-specific Input Terminal interface.(audio10.pdf section 4.3.2.1)
+    // so we may as well unify the parsing here.
+    protected byte mTerminalID;       // 3:1 ID of this Output Terminal. (0x02)
+    protected int mTerminalType;      // 4:2 USB Streaming. (0x0101)
+    protected byte mAssocTerminal;    // 6:1 Unused (0x00)
+
+    public UsbACTerminal(int length, byte type, byte subtype, byte subclass) {
+        super(length, type, subtype, subclass);
+    }
+
+    public byte getTerminalID() {
+        return mTerminalID;
+    }
+
+    public int getTerminalType() {
+        return mTerminalType;
+    }
+
+    public byte getAssocTerminal() {
+        return mAssocTerminal;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mTerminalID = stream.getByte();
+        mTerminalType = stream.unpackUsbWord();
+        mAssocTerminal = stream.getByte();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java
new file mode 100644
index 0000000..d7c84c6
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Format Interface.
+ *   Subclasses: UsbACFormatI and UsbACFormatII.
+ * see audio10.pdf section 4.5.3 & & Frmts10.pdf
+ */
+public abstract class UsbASFormat extends UsbACInterface {
+    private static final String TAG = "ASFormat";
+
+    private final byte mFormatType;   // 3:1 FORMAT_TYPE_*
+
+    public static final byte FORMAT_TYPE_I = 1;
+    public static final byte FORMAT_TYPE_II = 2;
+
+    public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) {
+        super(length, type, subtype, mSubclass);
+        mFormatType = formatType;
+    }
+
+    public byte getFormatType() {
+        return mFormatType;
+    }
+
+    /**
+     * Allocates the audio-class format subtype associated with the format type read from the
+     * stream.
+     */
+    public static UsbDescriptor allocDescriptor(ByteStream stream, int length, byte type,
+            byte subtype, byte subclass) {
+
+        byte formatType = stream.getByte();
+        //TODO
+        // There is an issue parsing format descriptors on (some) USB 2.0 pro-audio interfaces
+        // Since we don't need this info for headset detection, just skip these descriptors
+        // for now to avoid the (low) possibility of an IndexOutOfBounds exception.
+        switch (formatType) {
+//            case FORMAT_TYPE_I:
+//                return new UsbASFormatI(length, type, subtype, formatType, subclass);
+//
+//            case FORMAT_TYPE_II:
+//                return new UsbASFormatII(length, type, subtype, formatType, subclass);
+
+            default:
+                return null;
+        }
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormatI.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormatI.java
new file mode 100644
index 0000000..347a6cf
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormatI.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Format I interface.
+ * see Frmts10.pdf section 2.2
+ */
+public class UsbASFormatI extends UsbASFormat {
+    private static final String TAG = "ASFormatI";
+
+    private byte mNumChannels;      // 4:1
+    private byte mSubframeSize;     // 5:1 frame size in bytes
+    private byte mBitResolution;    // 6:1 sample size in bits
+    private byte mSampleFreqType;   // 7:1
+    private int[] mSampleRates;     // if mSamFreqType == 0, there will be 2 values: the
+                                    // min & max rates otherwise mSamFreqType rates.
+                                    // All 3-byte values. All rates in Hz
+
+    public UsbASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+        super(length, type, subtype, formatType, subclass);
+    }
+
+    public byte getNumChannels() {
+        return mNumChannels;
+    }
+
+    public byte getSubframeSize() {
+        return mSubframeSize;
+    }
+
+    public byte getBitResolution() {
+        return mBitResolution;
+    }
+
+    public byte getSampleFreqType() {
+        return mSampleFreqType;
+    }
+
+    public int[] getSampleRates() {
+        return mSampleRates;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mNumChannels = stream.getByte();
+        mSubframeSize = stream.getByte();
+        mBitResolution = stream.getByte();
+        mSampleFreqType = stream.getByte();
+        if (mSampleFreqType == 0) {
+            mSampleRates = new int[2];
+            mSampleRates[0] = stream.unpackUsbTriple();
+            mSampleRates[1] = stream.unpackUsbTriple();
+        } else {
+            mSampleRates = new int[mSampleFreqType];
+            for (int index = 0; index < mSampleFreqType; index++) {
+                mSampleRates[index] = stream.unpackUsbTriple();
+            }
+        }
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormatII.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormatII.java
new file mode 100644
index 0000000..abdc621
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormatII.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Format II interface.
+ * see Frmts10.pdf section 2.3
+ */
+public class UsbASFormatII extends UsbASFormat {
+    private static final String TAG = "ASFormatII";
+
+    private int mMaxBitRate; // 4:2 Indicates the maximum number of bits per second this
+                            // interface can handle. Expressed in kbits/s.
+    private int mSamplesPerFrame;   // 6:2 Indicates the number of PCM audio samples contained
+                                    // in one encoded audio frame.
+    private byte mSamFreqType;  // Indicates how the sampling frequency can be programmed:
+                                // 0: Continuous sampling frequency
+                                // 1..255: The number of discrete sampling frequencies supported
+                                // by the isochronous data endpoint of the AudioStreaming
+                                // interface (ns)
+    private int[] mSampleRates; // if mSamFreqType == 0, there will be 2 values:
+                                // the min & max rates. otherwise mSamFreqType rates.
+                                // All 3-byte values. All rates in Hz
+
+    public UsbASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+        super(length, type, subtype, formatType, subclass);
+    }
+
+    public int getMaxBitRate() {
+        return mMaxBitRate;
+    }
+
+    public int getSamplesPerFrame() {
+        return mSamplesPerFrame;
+    }
+
+    public byte getSamFreqType() {
+        return mSamFreqType;
+    }
+
+    public int[] getSampleRates() {
+        return mSampleRates;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mMaxBitRate = stream.unpackUsbWord();
+        mSamplesPerFrame = stream.unpackUsbWord();
+        mSamFreqType = stream.getByte();
+        int numFreqs = mSamFreqType == 0 ? 2 : mSamFreqType;
+        mSampleRates = new int[numFreqs];
+        for (int index = 0; index < numFreqs; index++) {
+            mSampleRates[index] = stream.unpackUsbTriple();
+        }
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/UsbASGeneral.java
new file mode 100644
index 0000000..c4f42d3
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbASGeneral.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific General interface.
+ * see audio10.pdf section 4.5.2
+ */
+public class UsbASGeneral extends UsbACInterface {
+    private static final String TAG = "ACGeneral";
+
+    // audio10.pdf - section 4.5.2
+    private byte mTerminalLink; // 3:1 The Terminal ID of the Terminal to which the endpoint
+                                // of this interface is connected.
+    private byte mDelay;        // 4:1 Delay introduced by the data path (see Section 3.4,
+                                // “Inter Channel Synchronization”). Expressed in number of frames.
+    private int mFormatTag;     // 5:2 The Audio Data Format that has to be used to communicate
+                                // with this interface.
+
+    public UsbASGeneral(int length, byte type, byte subtype, byte subclass) {
+        super(length, type, subtype, subclass);
+    }
+
+    public byte getTerminalLink() {
+        return mTerminalLink;
+    }
+
+    public byte getDelay() {
+        return mDelay;
+    }
+
+    public int getFormatTag() {
+        return mFormatTag;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mTerminalLink = stream.getByte();
+        mDelay = stream.getByte();
+        mFormatTag = stream.unpackUsbWord();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbBinaryParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbBinaryParser.java
new file mode 100644
index 0000000..185cee2
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbBinaryParser.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.hardware.usb.UsbDeviceConnection;
+import android.util.Log;
+
+import com.android.server.usb.descriptors.report.UsbStrings;
+
+/**
+ * @hide
+ * A class that just walks the descriptors and does a hex dump of the contained values.
+ * Usefull as a debugging tool.
+ */
+public class UsbBinaryParser {
+    private static final String TAG = "UsbBinaryParser";
+    private static final boolean LOGGING = false;
+
+    private void dumpDescriptor(ByteStream stream, int length, byte type, StringBuilder builder) {
+
+        // Log
+        if (LOGGING) {
+            Log.i(TAG, "l:" + length + " t:" + Integer.toHexString(type) + " "
+                    + UsbStrings.getDescriptorName(type));
+            StringBuilder sb = new StringBuilder();
+            for (int index = 2; index < length; index++) {
+                sb.append("0x" + Integer.toHexString(stream.getByte() & 0xFF) + " ");
+            }
+            Log.i(TAG, sb.toString());
+        } else {
+            // Screen Dump
+            builder.append("<p>");
+            builder.append("<b> l:" + length
+                    + " t:0x" + Integer.toHexString(type) + " "
+                    + UsbStrings.getDescriptorName(type) + "</b><br>");
+            for (int index = 2; index < length; index++) {
+                builder.append("0x" + Integer.toHexString(stream.getByte() & 0xFF) + " ");
+            }
+            builder.append("</p>");
+        }
+    }
+
+    /**
+     * Walk through descriptor stream and generate an HTML text report of the contents.
+     * TODO: This should be done in the model of UsbDescriptorsParser/Reporter model.
+     */
+    public void parseDescriptors(UsbDeviceConnection connection, byte[] descriptors,
+                                 StringBuilder builder) {
+
+        builder.append("<tt>");
+        ByteStream stream = new ByteStream(descriptors);
+        while (stream.available() > 0) {
+            int length = (int) stream.getByte() & 0x000000FF;
+            byte type = stream.getByte();
+            dumpDescriptor(stream, length, type, builder);
+        }
+        builder.append("</tt>");
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
new file mode 100644
index 0000000..8ae6d0f
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An USB Config Descriptor.
+ * see usb11.pdf section 9.6.2
+ */
+public class UsbConfigDescriptor extends UsbDescriptor {
+    private static final String TAG = "Config";
+
+    private int mTotalLength;   // 2:2 Total length in bytes of data returned
+    private byte mNumInterfaces; // 4:1 Number of Interfaces
+    private byte mConfigValue;  // 5:1 Value to use as an argument to select this configuration
+    private byte mConfigIndex;  // 6:1 Index of String Descriptor describing this configuration
+    private byte mAttribs;      // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
+                                //     D6 Self Powered
+                                //     D5 Remote Wakeup
+                                //     D4..0 Reserved, set to 0.
+    private byte mMaxPower;     // 8:1 Maximum Power Consumption in 2mA units
+
+    UsbConfigDescriptor(int length, byte type) {
+        super(length, type);
+    }
+
+    public int getTotalLength() {
+        return mTotalLength;
+    }
+
+    public byte getNumInterfaces() {
+        return mNumInterfaces;
+    }
+
+    public byte getConfigValue() {
+        return mConfigValue;
+    }
+
+    public byte getConfigIndex() {
+        return mConfigIndex;
+    }
+
+    public byte getAttribs() {
+        return mAttribs;
+    }
+
+    public byte getMaxPower() {
+        return mMaxPower;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mTotalLength = stream.unpackUsbWord();
+        mNumInterfaces = stream.getByte();
+        mConfigValue = stream.getByte();
+        mConfigIndex = stream.getByte();
+        mAttribs = stream.getByte();
+        mMaxPower = stream.getByte();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java
new file mode 100644
index 0000000..63b2d7f
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDeviceConnection;
+import android.util.Log;
+
+/*
+ * Some notes about UsbDescriptor and its subclasses.
+ *
+ * It is assumed that the user of the UsbDescriptorParser knows what they are doing
+ * so NO PROTECTION is implemented against "improper" use. Such uses are specifically:
+ * allocating a UsbDescriptor (subclass) object outside of the context of parsing/reading
+ * a rawdescriptor stream and perhaps accessing fields which have not been inialized (by
+ * parsing/reading or course).
+ */
+
+/**
+ * @hide
+ * Common superclass for all USB Descriptors.
+ */
+public abstract class UsbDescriptor {
+    private static final String TAG = "Descriptor";
+
+    protected final int mLength;    // 0:1 bLength Number Size of the Descriptor in Bytes (18 bytes)
+                                    // we store this as an int because Java bytes are SIGNED.
+    protected final byte mType;     // 1:1 bDescriptorType Constant Device Descriptor (0x01)
+
+    private byte[] mRawData;
+
+    private static final int SIZE_STRINGBUFFER = 256;
+    private static byte[] sStringBuffer = new byte[SIZE_STRINGBUFFER];
+
+    // Status
+    public static final int STATUS_UNPARSED         = 0;
+    public static final int STATUS_PARSED_OK        = 1;
+    public static final int STATUS_PARSED_UNDERRUN  = 2;
+    public static final int STATUS_PARSED_OVERRUN   = 3;
+    private int mStatus = STATUS_UNPARSED;
+
+    private static String[] sStatusStrings = {
+            "UNPARSED", "PARSED - OK", "PARSED - UNDERRUN", "PARSED - OVERRUN"};
+
+    // Descriptor Type IDs
+    public static final byte DESCRIPTORTYPE_DEVICE = 0x01;            // 1
+    public static final byte DESCRIPTORTYPE_CONFIG = 0x02;            // 2
+    public static final byte DESCRIPTORTYPE_STRING = 0x03;            // 3
+    public static final byte DESCRIPTORTYPE_INTERFACE = 0x04;         // 4
+    public static final byte DESCRIPTORTYPE_ENDPOINT = 0x05;          // 5
+    public static final byte DESCRIPTORTYPE_INTERFACEASSOC = 0x0B;    // 11
+    public static final byte DESCRIPTORTYPE_BOS = 0x0F;               // 15
+    public static final byte DESCRIPTORTYPE_CAPABILITY = 0x10;        // 16
+
+    public static final byte DESCRIPTORTYPE_HID = 0x21;                // 33
+    public static final byte DESCRIPTORTYPE_REPORT = 0x22;             // 34
+    public static final byte DESCRIPTORTYPE_PHYSICAL = 0x23;           // 35
+    public static final byte DESCRIPTORTYPE_AUDIO_INTERFACE = 0x24;    // 36
+    public static final byte DESCRIPTORTYPE_AUDIO_ENDPOINT = 0x25;     // 37
+    public static final byte DESCRIPTORTYPE_HUB = 0x29;                // 41
+    public static final byte DESCRIPTORTYPE_SUPERSPEED_HUB = 0x2A;     // 42
+    public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48
+
+    // Class IDs
+    public static final byte CLASSID_DEVICE  =      0x00;
+    public static final byte CLASSID_AUDIO =        0x01;
+    public static final byte CLASSID_COM =          0x02;
+    public static final byte CLASSID_HID =          0x03;
+    // public static final byte CLASSID_??? =       0x04;
+    public static final byte CLASSID_PHYSICAL =     0x05;
+    public static final byte CLASSID_IMAGE =        0x06;
+    public static final byte CLASSID_PRINTER =      0x07;
+    public static final byte CLASSID_STORAGE =      0x08;
+    public static final byte CLASSID_HUB =          0x09;
+    public static final byte CLASSID_CDC_CONTROL =  0x0A;
+    public static final byte CLASSID_SMART_CARD =   0x0B;
+    //public static final byte CLASSID_??? =        0x0C;
+    public static final byte CLASSID_SECURITY =     0x0D;
+    public static final byte CLASSID_VIDEO =        0x0E;
+    public static final byte CLASSID_HEALTHCARE =   0x0F;
+    public static final byte CLASSID_AUDIOVIDEO =   0x10;
+    public static final byte CLASSID_BILLBOARD =    0x11;
+    public static final byte CLASSID_TYPECBRIDGE =  0x12;
+    public static final byte CLASSID_DIAGNOSTIC =   (byte) 0xDC;
+    public static final byte CLASSID_WIRELESS =     (byte) 0xE0;
+    public static final byte CLASSID_MISC =         (byte) 0xEF;
+    public static final byte CLASSID_APPSPECIFIC =  (byte) 0xFE;
+    public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF;
+
+    // Audio Subclass codes
+    public static final byte AUDIO_SUBCLASS_UNDEFINED   = 0x00;
+    public static final byte AUDIO_AUDIOCONTROL         = 0x01;
+    public static final byte AUDIO_AUDIOSTREAMING       = 0x02;
+    public static final byte AUDIO_MIDISTREAMING        = 0x03;
+
+    // Request IDs
+    public static final int REQUEST_GET_STATUS         = 0x00;
+    public static final int REQUEST_CLEAR_FEATURE      = 0x01;
+    public static final int REQUEST_SET_FEATURE        = 0x03;
+    public static final int REQUEST_GET_ADDRESS        = 0x05;
+    public static final int REQUEST_GET_DESCRIPTOR     = 0x06;
+    public static final int REQUEST_SET_DESCRIPTOR     = 0x07;
+    public static final int REQUEST_GET_CONFIGURATION  = 0x08;
+    public static final int REQUEST_SET_CONFIGURATION  = 0x09;
+
+    /**
+     * @throws IllegalArgumentException
+     */
+    UsbDescriptor(int length, byte type) {
+        // a descriptor has at least a length byte and type byte
+        // one could imagine an empty one otherwise
+        if (length < 2) {
+            // huh?
+            throw new IllegalArgumentException();
+        }
+
+        mLength = length;
+        mType = type;
+    }
+
+    public int getLength() {
+        return mLength;
+    }
+
+    public byte getType() {
+        return mType;
+    }
+
+    public int getStatus() {
+        return mStatus;
+    }
+
+    public void setStatus(int status) {
+        mStatus = status;
+    }
+
+    public String getStatusString() {
+        return sStatusStrings[mStatus];
+    }
+
+    public byte[] getRawData() {
+        return mRawData;
+    }
+
+    /**
+     * Called by the parser for any necessary cleanup.
+     */
+    public void postParse(ByteStream stream) {
+        // Status
+        int bytesRead = stream.getReadCount();
+        if (bytesRead < mLength) {
+            // Too cold...
+            stream.advance(mLength - bytesRead);
+            mStatus = STATUS_PARSED_UNDERRUN;
+            Log.w(TAG, "UNDERRUN t:0x" + Integer.toHexString(mType)
+                    + " r:" + bytesRead + " < l:" + mLength);
+        } else if (bytesRead > mLength) {
+            // Too hot...
+            stream.reverse(bytesRead - mLength);
+            mStatus = STATUS_PARSED_OVERRUN;
+            Log.w(TAG, "OVERRRUN t:0x" + Integer.toHexString(mType)
+                    + " r:" + bytesRead + " > l:" + mLength);
+        } else {
+            // Just right!
+            mStatus = STATUS_PARSED_OK;
+        }
+    }
+
+    /**
+     * Reads data fields from specified raw-data stream.
+     */
+    public int parseRawDescriptors(ByteStream stream) {
+        int numRead = stream.getReadCount();
+        int dataLen = mLength - numRead;
+        if (dataLen > 0) {
+            mRawData = new byte[dataLen];
+            for (int index = 0; index < dataLen; index++) {
+                mRawData[index] = stream.getByte();
+            }
+        }
+        return mLength;
+    }
+
+    /**
+     * Gets a string for the specified index from the USB Device's string descriptors.
+     */
+    public static String getUsbDescriptorString(UsbDeviceConnection connection, byte strIndex) {
+        String usbStr = "";
+        if (strIndex != 0) {
+            try {
+                int rdo = connection.controlTransfer(
+                        UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD,
+                        REQUEST_GET_DESCRIPTOR,
+                        (DESCRIPTORTYPE_STRING << 8) | strIndex,
+                        0,
+                        sStringBuffer,
+                        0xFF,
+                        0);
+                if (rdo >= 0) {
+                    usbStr = new String(sStringBuffer, 2, rdo - 2, "UTF-16LE");
+                } else {
+                    usbStr = "?";
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Can not communicate with USB device", e);
+            }
+        }
+        return usbStr;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
new file mode 100644
index 0000000..7c074da
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ * Class for parsing a binary stream of USB Descriptors.
+ */
+public class UsbDescriptorParser {
+    private static final String TAG = "DescriptorParser";
+
+    // Descriptor Objects
+    private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>();
+
+    private UsbDeviceDescriptor mDeviceDescriptor;
+    private UsbInterfaceDescriptor mCurInterfaceDescriptor;
+
+    public UsbDescriptorParser() {}
+
+    /**
+     * The probability (as returned by getHeadsetProbability() at which we conclude
+     * the peripheral is a headset.
+     */
+    private static final float IN_HEADSET_TRIGGER = 0.75f;
+    private static final float OUT_HEADSET_TRIGGER = 0.75f;
+
+    private UsbDescriptor allocDescriptor(ByteStream stream) {
+        stream.resetReadCount();
+
+        int length = (int) stream.getByte() & 0x000000FF;
+        byte type = stream.getByte();
+
+        UsbDescriptor descriptor = null;
+        switch (type) {
+            /*
+             * Standard
+             */
+            case UsbDescriptor.DESCRIPTORTYPE_DEVICE:
+                descriptor = mDeviceDescriptor = new UsbDeviceDescriptor(length, type);
+                break;
+
+            case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
+                descriptor = new UsbConfigDescriptor(length, type);
+                break;
+
+            case UsbDescriptor.DESCRIPTORTYPE_INTERFACE:
+                descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type);
+                break;
+
+            case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
+                descriptor = new UsbEndpointDescriptor(length, type);
+                break;
+
+            /*
+             * HID
+             */
+            case UsbDescriptor.DESCRIPTORTYPE_HID:
+                descriptor = new UsbHIDDescriptor(length, type);
+                break;
+
+            /*
+             * Other
+             */
+            case UsbDescriptor.DESCRIPTORTYPE_INTERFACEASSOC:
+                descriptor = new UsbInterfaceAssoc(length, type);
+                break;
+
+            /*
+             * Audio Class Specific
+             */
+            case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE:
+                descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
+                break;
+
+            case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT:
+                descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
+                break;
+
+            default:
+                break;
+        }
+
+        if (descriptor == null) {
+            // Unknown Descriptor
+            Log.i(TAG, "Unknown Descriptor len:" + length + " type:0x"
+                    + Integer.toHexString(type));
+            descriptor = new UsbUnknown(length, type);
+        }
+
+        return descriptor;
+    }
+
+    public UsbDeviceDescriptor getDeviceDescriptor() {
+        return mDeviceDescriptor;
+    }
+
+    public UsbInterfaceDescriptor getCurInterface() {
+        return mCurInterfaceDescriptor;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean parseDescriptors(byte[] descriptors) {
+        try {
+            mDescriptors.clear();
+
+            ByteStream stream = new ByteStream(descriptors);
+            while (stream.available() > 0) {
+                UsbDescriptor descriptor = allocDescriptor(stream);
+                if (descriptor != null) {
+                    // Parse
+                    descriptor.parseRawDescriptors(stream);
+                    mDescriptors.add(descriptor);
+
+                    // Clean up
+                    descriptor.postParse(stream);
+                }
+            }
+            return true;
+        } catch (Exception ex) {
+            Log.e(TAG, "Exception parsing USB descriptors.", ex);
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean parseDevice(String deviceAddr) {
+        byte[] rawDescriptors = getRawDescriptors(deviceAddr);
+        return rawDescriptors != null && parseDescriptors(rawDescriptors);
+    }
+
+    private native byte[] getRawDescriptors(String deviceAddr);
+
+    public int getParsingSpec() {
+        return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
+    }
+
+    public ArrayList<UsbDescriptor> getDescriptors() {
+        return mDescriptors;
+    }
+
+    /**
+     * @hide
+     */
+    public ArrayList<UsbDescriptor> getDescriptors(byte type) {
+        ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
+        for (UsbDescriptor descriptor : mDescriptors) {
+            if (descriptor.getType() == type) {
+                list.add(descriptor);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * @hide
+     */
+    public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) {
+        ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
+        for (UsbDescriptor descriptor : mDescriptors) {
+            // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE
+            if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_INTERFACE) {
+                if (descriptor instanceof UsbInterfaceDescriptor) {
+                    UsbInterfaceDescriptor intrDesc = (UsbInterfaceDescriptor) descriptor;
+                    if (intrDesc.getUsbClass() == usbClass) {
+                        list.add(descriptor);
+                    }
+                } else {
+                    Log.w(TAG, "Unrecognized Interface l:" + descriptor.getLength()
+                            + " t:0x" + Integer.toHexString(descriptor.getType()));
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * @hide
+     */
+    public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) {
+        ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
+        for (UsbDescriptor descriptor : mDescriptors) {
+            if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) {
+                // ensure that this isn't an unrecognized DESCRIPTORTYPE_AUDIO_INTERFACE
+                if (descriptor instanceof UsbACInterface) {
+                    UsbACInterface acDescriptor = (UsbACInterface) descriptor;
+                    if (acDescriptor.getSubtype() == subtype
+                            && acDescriptor.getSubclass() == subclass) {
+                        list.add(descriptor);
+                    }
+                } else {
+                    Log.w(TAG, "Unrecognized Audio Interface l:" + descriptor.getLength()
+                            + " t:0x" + Integer.toHexString(descriptor.getType()));
+                }
+            }
+        }
+        return list;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasHIDDescriptor() {
+        ArrayList<UsbDescriptor> descriptors =
+                getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID);
+        return !descriptors.isEmpty();
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasMIDIInterface() {
+        ArrayList<UsbDescriptor> descriptors =
+                getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
+        for (UsbDescriptor descriptor : descriptors) {
+            // enusure that this isn't an unrecognized interface descriptor
+            if (descriptor instanceof UsbInterfaceDescriptor) {
+                UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor;
+                if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
+                    return true;
+                }
+            } else {
+                Log.w(TAG, "Undefined Audio Class Interface l:" + descriptor.getLength()
+                        + " t:0x" + Integer.toHexString(descriptor.getType()));
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public float getInputHeadsetProbability() {
+        if (hasMIDIInterface()) {
+            return 0.0f;
+        }
+
+        float probability = 0.0f;
+        ArrayList<UsbDescriptor> acDescriptors;
+
+        // Look for a microphone
+        boolean hasMic = false;
+        acDescriptors = getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL,
+                UsbACInterface.AUDIO_AUDIOCONTROL);
+        for (UsbDescriptor descriptor : acDescriptors) {
+            if (descriptor instanceof UsbACInputTerminal) {
+                UsbACInputTerminal inDescr = (UsbACInputTerminal) descriptor;
+                if (inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_IN_MIC
+                        || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET
+                        || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED
+                        || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_LINE) {
+                    hasMic = true;
+                    break;
+                }
+            } else {
+                Log.w(TAG, "Undefined Audio Input terminal l:" + descriptor.getLength()
+                        + " t:0x" + Integer.toHexString(descriptor.getType()));
+            }
+        }
+
+        // Look for a "speaker"
+        boolean hasSpeaker = false;
+        acDescriptors =
+                getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
+                        UsbACInterface.AUDIO_AUDIOCONTROL);
+        for (UsbDescriptor descriptor : acDescriptors) {
+            if (descriptor instanceof UsbACOutputTerminal) {
+                UsbACOutputTerminal outDescr = (UsbACOutputTerminal) descriptor;
+                if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER
+                        || outDescr.getTerminalType()
+                            == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
+                        || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
+                    hasSpeaker = true;
+                    break;
+                }
+            } else {
+                Log.w(TAG, "Undefined Audio Output terminal l:" + descriptor.getLength()
+                        + " t:0x" + Integer.toHexString(descriptor.getType()));
+            }
+        }
+
+        if (hasMic && hasSpeaker) {
+            probability += 0.75f;
+        }
+
+        if (hasMic && hasHIDDescriptor()) {
+            probability += 0.25f;
+        }
+
+        return probability;
+    }
+
+    /**
+     * getInputHeadsetProbability() reports a probability of a USB Input peripheral being a
+     * headset. The probability range is between 0.0f (definitely NOT a headset) and
+     * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
+     * to count on the peripheral being a headset.
+     */
+    public boolean isInputHeadset() {
+        return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
+    }
+
+    /**
+     * @hide
+     */
+    public float getOutputHeadsetProbability() {
+        if (hasMIDIInterface()) {
+            return 0.0f;
+        }
+
+        float probability = 0.0f;
+        ArrayList<UsbDescriptor> acDescriptors;
+
+        // Look for a "speaker"
+        boolean hasSpeaker = false;
+        acDescriptors =
+                getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
+                        UsbACInterface.AUDIO_AUDIOCONTROL);
+        for (UsbDescriptor descriptor : acDescriptors) {
+            if (descriptor instanceof UsbACOutputTerminal) {
+                UsbACOutputTerminal outDescr = (UsbACOutputTerminal) descriptor;
+                if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER
+                        || outDescr.getTerminalType()
+                            == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
+                        || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
+                    hasSpeaker = true;
+                    break;
+                }
+            } else {
+                Log.w(TAG, "Undefined Audio Output terminal l:" + descriptor.getLength()
+                        + " t:0x" + Integer.toHexString(descriptor.getType()));
+            }
+        }
+
+        if (hasSpeaker) {
+            probability += 0.75f;
+        }
+
+        if (hasSpeaker && hasHIDDescriptor()) {
+            probability += 0.25f;
+        }
+
+        return probability;
+    }
+
+    /**
+     * getOutputHeadsetProbability() reports a probability of a USB Output peripheral being a
+     * headset. The probability range is between 0.0f (definitely NOT a headset) and
+     * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient
+     * to count on the peripheral being a headset.
+     */
+    public boolean isOutputHeadset() {
+        return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
+    }
+
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
new file mode 100644
index 0000000..90848ca
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * A USB Device Descriptor.
+ * see usb11.pdf section 9.6.1
+ */
+/* public */ public class UsbDeviceDescriptor extends UsbDescriptor {
+    private static final String TAG = "Device";
+
+    private int mSpec;          // 2:2 bcdUSB 2 BCD USB Specification Number - BCD
+    private byte mDevClass;     // 4:1 class code
+    private byte mDevSubClass;  // 5:1 subclass code
+    private byte mProtocol;     // 6:1 protocol
+    private byte mPacketSize;   // 7:1 Maximum Packet Size for Zero Endpoint.
+                                // Valid Sizes are 8, 16, 32, 64
+    private int mVendorID;      // 8:2 vendor ID
+    private int mProductID;     // 10:2 product ID
+    private int mDeviceRelease; // 12:2 Device Release number - BCD
+    private byte mMfgIndex;     // 14:1 Index of Manufacturer String Descriptor
+    private byte mProductIndex; // 15:1 Index of Product String Descriptor
+    private byte mSerialNum;    // 16:1 Index of Serial Number String Descriptor
+    private byte mNumConfigs;   // 17:1 Number of Possible Configurations
+
+    UsbDeviceDescriptor(int length, byte type) {
+        super(length, type);
+    }
+
+    public int getSpec() {
+        return mSpec;
+    }
+
+    public byte getDevClass() {
+        return mDevClass;
+    }
+
+    public byte getDevSubClass() {
+        return mDevSubClass;
+    }
+
+    public byte getProtocol() {
+        return mProtocol;
+    }
+
+    public byte getPacketSize() {
+        return mPacketSize;
+    }
+
+    public int getVendorID() {
+        return mVendorID;
+    }
+
+    public int getProductID() {
+        return mProductID;
+    }
+
+    public int getDeviceRelease() {
+        return mDeviceRelease;
+    }
+
+    public byte getMfgIndex() {
+        return mMfgIndex;
+    }
+
+    public byte getProductIndex() {
+        return mProductIndex;
+    }
+
+    public byte getSerialNum() {
+        return mSerialNum;
+    }
+
+    public byte getNumConfigs() {
+        return mNumConfigs;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mSpec = stream.unpackUsbWord();
+        mDevClass = stream.getByte();
+        mDevSubClass = stream.getByte();
+        mProtocol = stream.getByte();
+        mPacketSize = stream.getByte();
+        mVendorID = stream.unpackUsbWord();
+        mProductID = stream.unpackUsbWord();
+        mDeviceRelease = stream.unpackUsbWord();
+        mMfgIndex = stream.getByte();
+        mProductIndex = stream.getByte();
+        mSerialNum = stream.getByte();
+        mNumConfigs = stream.getByte();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
new file mode 100644
index 0000000..def6700
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * A Usb Endpoint Descriptor.
+ * see usb11.pdf section 9.6.4
+ */
+public class UsbEndpointDescriptor extends UsbDescriptor {
+    private static final String TAG = "EndPoint";
+
+    public static final byte MASK_ENDPOINT_ADDRESS     = 0b0001111;
+    public static final byte MASK_ENDPOINT_DIRECTION   = (byte) 0b10000000;
+    public static final byte DIRECTION_OUTPUT          = 0x00;
+    public static final byte DIRECTION_INPUT           = (byte) 0x80;
+
+    public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011;
+    public static final byte TRANSTYPE_CONTROL     = 0x00;
+    public static final byte TRANSTYPE_ISO         = 0x01;
+    public static final byte TRANSTYPE_BULK        = 0x02;
+    public static final byte TRANSTYPE_INTERRUPT   = 0x03;
+
+    public static final byte MASK_ATTRIBS_SYNCTYPE  = 0b00001100;
+    public static final byte SYNCTYPE_NONE          = 0b00000000;
+    public static final byte SYNCTYPE_ASYNC         = 0b00000100;
+    public static final byte SYNCTYPE_ADAPTSYNC     = 0b00001000;
+    public static final byte SYNCTYPE_RESERVED      = 0b00001100;
+
+    public static final byte MASK_ATTRIBS_USEAGE    = 0b00110000;
+    public static final byte USEAGE_DATA            = 0b00000000;
+    public static final byte USEAGE_FEEDBACK        = 0b00010000;
+    public static final byte USEAGE_EXPLICIT        = 0b00100000;
+    public static final byte USEAGE_RESERVED        = 0b00110000;
+
+    private byte mEndpointAddress;  // 2:1 Endpoint Address
+                                    // Bits 0..3b Endpoint Number.
+                                    // Bits 4..6b Reserved. Set to Zero
+                                    // Bits 7 Direction 0 = Out, 1 = In
+                                    // (Ignored for Control Endpoints)
+    private byte mAttributes;   // 3:1 Various flags
+                                // Bits 0..1 Transfer Type:
+                                //     00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt
+                                // Bits 2..7 are reserved. If Isochronous endpoint,
+                                // Bits 3..2 = Synchronisation Type (Iso Mode)
+                                //  00 = No Synchonisation
+                                //  01 = Asynchronous
+                                //  10 = Adaptive
+                                //  11 = Synchronous
+                                // Bits 5..4 = Usage Type (Iso Mode)
+                                //  00: Data Endpoint
+                                //  01:Feedback Endpoint 10
+                                //  Explicit Feedback Data Endpoint
+                                //  11: Reserved
+    private int mPacketSize;    // 4:2 Maximum Packet Size this endpoint is capable of
+                                // sending or receiving
+    private byte mInterval;     // 6:1 Interval for polling endpoint data transfers. Value in
+                                // frame counts.
+                                // Ignored for Bulk & Control Endpoints. Isochronous must equal
+                                // 1 and field may range from 1 to 255 for interrupt endpoints.
+    private byte mRefresh;
+    private byte mSyncAddress;
+
+    public UsbEndpointDescriptor(int length, byte type) {
+        super(length, type);
+    }
+
+    public byte getEndpointAddress() {
+        return mEndpointAddress;
+    }
+
+    public byte getAttributes() {
+        return mAttributes;
+    }
+
+    public int getPacketSize() {
+        return mPacketSize;
+    }
+
+    public byte getInterval() {
+        return mInterval;
+    }
+
+    public byte getRefresh() {
+        return mRefresh;
+    }
+
+    public byte getSyncAddress() {
+        return mSyncAddress;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mEndpointAddress = stream.getByte();
+        mAttributes = stream.getByte();
+        mPacketSize = stream.unpackUsbWord();
+        mInterval = stream.getByte();
+        if (mLength == 9) {
+            mRefresh = stream.getByte();
+            mSyncAddress = stream.getByte();
+        }
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbHIDDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbHIDDescriptor.java
new file mode 100644
index 0000000..56c07ec
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbHIDDescriptor.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * A USB HID (Human Interface Descriptor).
+ * see HID1_11.pdf - 6.2.1
+ */
+public class UsbHIDDescriptor extends UsbDescriptor {
+    private static final String TAG = "HID";
+
+    private int mRelease;           // 2:2 the HID Class Specification release.
+    private byte mCountryCode;      // 4:1 country code of the localized hardware.
+    private byte mNumDescriptors;   // number of descriptors (always at least one
+                                    // i.e. Report descriptor.)
+    private byte mDescriptorType;   // 6:1 type of class descriptor.
+                                    // See Section 7.1.2: Set_Descriptor
+                                    // Request for a table of class descriptor constants.
+    private int mDescriptorLen;     // 7:2 Numeric expression that is the total size of
+                                    // the Report descriptor.
+
+    public UsbHIDDescriptor(int length, byte type) {
+        super(length, type);
+    }
+
+    public int getRelease() {
+        return mRelease;
+    }
+
+    public byte getCountryCode() {
+        return mCountryCode;
+    }
+
+    public byte getNumDescriptors() {
+        return mNumDescriptors;
+    }
+
+    public byte getDescriptorType() {
+        return mDescriptorType;
+    }
+
+    public int getDescriptorLen() {
+        return mDescriptorLen;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mRelease = stream.unpackUsbWord();
+        mCountryCode = stream.getByte();
+        mNumDescriptors = stream.getByte();
+        mDescriptorType = stream.getByte();
+        mDescriptorLen = stream.unpackUsbWord();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceAssoc.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceAssoc.java
new file mode 100644
index 0000000..4b18a01
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceAssoc.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * A USB Interface Association Descriptor.
+ * found this one here: http://www.usb.org/developers/docs/whitepapers/iadclasscode_r10.pdf
+ * also: https://msdn.microsoft.com/en-us/library/windows/hardware/ff540054(v=vs.85).aspx
+ */
+public class UsbInterfaceAssoc extends UsbDescriptor {
+    private static final String TAG = "InterfaceAssoc";
+
+    private byte mFirstInterface;
+    private byte mInterfaceCount;
+    private byte mFunctionClass;
+    private byte mFunctionSubClass;
+    private byte mFunctionProtocol;
+    private byte mFunction;
+
+    public UsbInterfaceAssoc(int length, byte type) {
+        super(length, type);
+    }
+
+    public byte getFirstInterface() {
+        return mFirstInterface;
+    }
+
+    public byte getInterfaceCount() {
+        return mInterfaceCount;
+    }
+
+    public byte getFunctionClass() {
+        return mFunctionClass;
+    }
+
+    public byte getFunctionSubClass() {
+        return mFunctionSubClass;
+    }
+
+    public byte getFunctionProtocol() {
+        return mFunctionProtocol;
+    }
+
+    public byte getFunction() {
+        return mFunction;
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mFirstInterface = stream.getByte();
+        mInterfaceCount = stream.getByte();
+        mFunctionClass = stream.getByte();
+        mFunctionSubClass = stream.getByte();
+        mFunctionProtocol = stream.getByte();
+        mFunction = stream.getByte();
+
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
new file mode 100644
index 0000000..21b5e0c
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * A common super-class for all USB Interface Descritor subtypes.
+ * see usb11.pdf section 9.6.3
+ */
+public class UsbInterfaceDescriptor extends UsbDescriptor {
+    private static final String TAG = "Interface";
+
+    protected byte mInterfaceNumber;  // 2:1 Number of Interface
+    protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
+    protected byte mNumEndpoints;     // 4:1 Number of Endpoints used for this interface
+    protected byte mUsbClass;         // 5:1 Class Code
+    protected byte mUsbSubclass;      // 6:1 Subclass Code
+    protected byte mProtocol;         // 7:1 Protocol Code
+    protected byte mDescrIndex;       // 8:1 Index of String Descriptor Describing this interface
+
+    UsbInterfaceDescriptor(int length, byte type) {
+        super(length, type);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        mInterfaceNumber = stream.getByte();
+        mAlternateSetting = stream.getByte();
+        mNumEndpoints = stream.getByte();
+        mUsbClass = stream.getByte();
+        mUsbSubclass = stream.getByte();
+        mProtocol = stream.getByte();
+        mDescrIndex = stream.getByte();
+
+        return mLength;
+    }
+
+    public byte getInterfaceNumber() {
+        return mInterfaceNumber;
+    }
+
+    public byte getAlternateSetting() {
+        return mAlternateSetting;
+    }
+
+    public byte getNumEndpoints() {
+        return mNumEndpoints;
+    }
+
+    public byte getUsbClass() {
+        return mUsbClass;
+    }
+
+    public byte getUsbSubclass() {
+        return mUsbSubclass;
+    }
+
+    public byte getProtocol() {
+        return mProtocol;
+    }
+
+    public byte getDescrIndex() {
+        return mDescrIndex;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
new file mode 100644
index 0000000..4452b23
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Midi Streaming Interface.
+ * see midi10.pdf section 6.1.2.1
+ */
+public class UsbMSMidiHeader extends UsbACInterface {
+    private static final String TAG = "MSMidiHeader";
+
+    public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) {
+        super(length, type, subtype, subclass);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        // TODO - read data memebers
+        stream.advance(mLength - stream.getReadCount());
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
new file mode 100644
index 0000000..2d33ba7
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Midi Input Jack Interface.
+ * see midi10.pdf section B.4.3
+ */
+public class UsbMSMidiInputJack extends UsbACInterface {
+    private static final String TAG = "MSMidiInputJack";
+
+    UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) {
+        super(length, type, subtype, subclass);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        // TODO - read data memebers
+        stream.advance(mLength - stream.getReadCount());
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
new file mode 100644
index 0000000..bd2dc11
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * An audio class-specific Midi Output Jack Interface.
+ * see midi10.pdf section B.4.4
+ */
+public class UsbMSMidiOutputJack extends UsbACInterface {
+    private static final String TAG = "MSMidiOutputJack";
+
+    public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) {
+        super(length, type, subtype, subclass);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        // TODO - read data memebers
+        stream.advance(mLength - stream.getReadCount());
+        return mLength;
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java b/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java
new file mode 100644
index 0000000..b521462
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * A class for decoding information in Terminal Descriptors.
+ * see termt10.pdf
+ */
+public class UsbTerminalTypes {
+    private static final String TAG = "TerminalTypes";
+
+    // USB
+    public static final int TERMINAL_USB_STREAMING   = 0x0101;
+
+    // Inputs
+    public static final int TERMINAL_IN_UNDEFINED    = 0x0200;
+    public static final int TERMINAL_IN_MIC          = 0x0201;
+    public static final int TERMINAL_IN_DESKTOP_MIC  = 0x0202;
+    public static final int TERMINAL_IN_PERSONAL_MIC = 0x0203;
+    public static final int TERMINAL_IN_OMNI_MIC     = 0x0204;
+    public static final int TERMINAL_IN_MIC_ARRAY    = 0x0205;
+    public static final int TERMINAL_IN_PROC_MIC_ARRAY = 0x0206;
+
+    // Outputs
+    public static final int TERMINAL_OUT_UNDEFINED       = 0x0300;
+    public static final int TERMINAL_OUT_SPEAKER         = 0x0301;
+    public static final int TERMINAL_OUT_HEADPHONES      = 0x0302;
+    public static final int TERMINAL_OUT_HEADMOUNTED     = 0x0303;
+    public static final int TERMINAL_OUT_DESKTOPSPEAKER  = 0x0304;
+    public static final int TERMINAL_OUT_ROOMSPEAKER     = 0x0305;
+    public static final int TERMINAL_OUT_COMSPEAKER      = 0x0306;
+    public static final int TERMINAL_OUT_LFSPEAKER       = 0x0307;
+
+    // Bi-directional
+    public static final int TERMINAL_BIDIR_UNDEFINED    = 0x0400;
+    public static final int TERMINAL_BIDIR_HANDSET      = 0x0401;
+    public static final int TERMINAL_BIDIR_HEADSET      = 0x0402;
+    public static final int TERMINAL_BIDIR_SKRPHONE     = 0x0403;
+    public static final int TERMINAL_BIDIR_SKRPHONE_SUPRESS = 0x0404;
+    public static final int TERMINAL_BIDIR_SKRPHONE_CANCEL = 0x0405;
+
+    // Telephony
+    public static final int TERMINAL_TELE_UNDEFINED     = 0x0500;
+    public static final int TERMINAL_TELE_PHONELINE     = 0x0501;
+    public static final int TERMINAL_TELE_PHONE         = 0x0502;
+    public static final int TERMINAL_TELE_DOWNLINEPHONE = 0x0503;
+
+    // External
+    public static final int TERMINAL_EXTERN_UNDEFINED   = 0x0600;
+    public static final int TERMINAL_EXTERN_ANALOG      = 0x0601;
+    public static final int TERMINAL_EXTERN_DIGITAL     = 0x0602;
+    public static final int TERMINAL_EXTERN_LINE        = 0x0603;
+    public static final int TERMINAL_EXTERN_LEGACY      = 0x0604;
+    public static final int TERMINAL_EXTERN_SPIDF       = 0x0605;
+    public static final int TERMINAL_EXTERN_1394DA      = 0x0606;
+    public static final int TERMINAL_EXTERN_1394DV      = 0x0607;
+
+    public static final int TERMINAL_EMBED_UNDEFINED    = 0x0700;
+    public static final int TERMINAL_EMBED_CALNOISE     = 0x0701;
+    public static final int TERMINAL_EMBED_EQNOISE      = 0x0702;
+    public static final int TERMINAL_EMBED_CDPLAYER     = 0x0703;
+    public static final int TERMINAL_EMBED_DAT          = 0x0704;
+    public static final int TERMINAL_EMBED_DCC          = 0x0705;
+    public static final int TERMINAL_EMBED_MINIDISK     = 0x0706;
+    public static final int TERMINAL_EMBED_ANALOGTAPE   = 0x0707;
+    public static final int TERMINAL_EMBED_PHONOGRAPH   = 0x0708;
+    public static final int TERMINAL_EMBED_VCRAUDIO     = 0x0709;
+    public static final int TERMINAL_EMBED_VIDDISKAUDIO = 0x070A;
+    public static final int TERMINAL_EMBED_DVDAUDIO     = 0x070B;
+    public static final int TERMINAL_EMBED_TVAUDIO      = 0x070C;
+    public static final int TERMINAL_EMBED_SATELLITEAUDIO = 0x070D;
+    public static final int TERMINAL_EMBED_CABLEAUDIO   = 0x070E;
+    public static final int TERMINAL_EMBED_DSSAUDIO     = 0x070F;
+    public static final int TERMINAL_EMBED_RADIOAUDIO   = 0x0710;
+    public static final int TERMINAL_EMBED_RADIOTRANSMITTER = 0x0711;
+    public static final int TERMINAL_EMBED_MULTITRACK   = 0x0712;
+    public static final int TERMINAL_EMBED_SYNTHESIZER  = 0x0713;
+
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbUnknown.java b/services/usb/java/com/android/server/usb/descriptors/UsbUnknown.java
new file mode 100644
index 0000000..a6fe8bb
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbUnknown.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * A holder for any unrecognized descriptor encountered in the descriptor stream.
+ */
+public class UsbUnknown extends UsbDescriptor {
+    static final String TAG = "Unknown";
+
+    public UsbUnknown(int length, byte type) {
+        super(length, type);
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/HTMLReporter.java b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReporter.java
new file mode 100644
index 0000000..c98789d
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReporter.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors.report;
+
+import android.hardware.usb.UsbDeviceConnection;
+
+import com.android.server.usb.descriptors.UsbACAudioControlEndpoint;
+import com.android.server.usb.descriptors.UsbACAudioStreamEndpoint;
+import com.android.server.usb.descriptors.UsbACFeatureUnit;
+import com.android.server.usb.descriptors.UsbACHeader;
+import com.android.server.usb.descriptors.UsbACInputTerminal;
+import com.android.server.usb.descriptors.UsbACInterface;
+import com.android.server.usb.descriptors.UsbACMidiEndpoint;
+import com.android.server.usb.descriptors.UsbACMixerUnit;
+import com.android.server.usb.descriptors.UsbACOutputTerminal;
+import com.android.server.usb.descriptors.UsbACSelectorUnit;
+import com.android.server.usb.descriptors.UsbACTerminal;
+import com.android.server.usb.descriptors.UsbASFormat;
+import com.android.server.usb.descriptors.UsbASFormatI;
+import com.android.server.usb.descriptors.UsbASFormatII;
+import com.android.server.usb.descriptors.UsbASGeneral;
+import com.android.server.usb.descriptors.UsbConfigDescriptor;
+import com.android.server.usb.descriptors.UsbDescriptor;
+import com.android.server.usb.descriptors.UsbDeviceDescriptor;
+import com.android.server.usb.descriptors.UsbEndpointDescriptor;
+import com.android.server.usb.descriptors.UsbHIDDescriptor;
+import com.android.server.usb.descriptors.UsbInterfaceAssoc;
+import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
+import com.android.server.usb.descriptors.UsbMSMidiHeader;
+import com.android.server.usb.descriptors.UsbMSMidiInputJack;
+import com.android.server.usb.descriptors.UsbMSMidiOutputJack;
+import com.android.server.usb.descriptors.UsbUnknown;
+
+/**
+ * Implements the Reporter inteface to provide HTML reporting for UsbDescriptor subclasses.
+ */
+public class HTMLReporter implements Reporter {
+    private final StringBuilder mStringBuilder;
+    private final UsbDeviceConnection mConnection;
+
+    public HTMLReporter(StringBuilder stringBuilder, UsbDeviceConnection connection) {
+        mStringBuilder = stringBuilder;
+        mConnection = connection;
+    }
+
+    /*
+     * HTML Helpers
+     */
+    private void writeHeader(int level, String text) {
+        mStringBuilder
+                .append("<h").append(level).append('>')
+                .append(text)
+                .append("</h").append(level).append('>');
+    }
+
+    private void openParagraph() {
+        mStringBuilder.append("<p>");
+    }
+
+    private void closeParagraph() {
+        mStringBuilder.append("</p>");
+    }
+
+    private void writeParagraph(String text) {
+        openParagraph();
+        mStringBuilder.append(text);
+        closeParagraph();
+    }
+
+    private void openList() {
+        mStringBuilder.append("<ul>");
+    }
+
+    private void closeList() {
+        mStringBuilder.append("</ul>");
+    }
+
+    private void openListItem() {
+        mStringBuilder.append("<li>");
+    }
+
+    private void closeListItem() {
+        mStringBuilder.append("</li>");
+    }
+
+    private void writeListItem(String text) {
+        openListItem();
+        mStringBuilder.append(text);
+        closeListItem();
+    }
+
+    /*
+     * Data Formating Helpers
+     */
+    private static String getHexString(byte value) {
+        return "0x" + Integer.toHexString(((int) value) & 0xFF).toUpperCase();
+    }
+
+    private static String getBCDString(int value) {
+        int major = value >> 8;
+        int minor = (value >> 4) & 0x0F;
+        int subminor = value & 0x0F;
+
+        return "" + major + "." + minor + subminor;
+    }
+
+    private static String getHexString(int value) {
+        int intValue = value & 0xFFFF;
+        return "0x" + Integer.toHexString(intValue).toUpperCase();
+    }
+
+    private void dumpHexArray(byte[] rawData, StringBuilder builder) {
+        if (rawData != null) {
+            // Assume the type and Length and perhaps sub-type have been displayed
+            openParagraph();
+            for (int index = 0; index < rawData.length; index++) {
+                builder.append(getHexString(rawData[index]) + " ");
+            }
+            closeParagraph();
+        }
+    }
+
+    /**
+     * Decode ACTUAL UsbDescriptor sub classes and call type-specific report methods.
+     */
+    @Override
+    public void report(UsbDescriptor descriptor) {
+        if (descriptor instanceof UsbDeviceDescriptor) {
+            tsReport((UsbDeviceDescriptor) descriptor);
+        } else if (descriptor instanceof UsbConfigDescriptor) {
+            tsReport((UsbConfigDescriptor) descriptor);
+        } else if (descriptor instanceof UsbInterfaceDescriptor) {
+            tsReport((UsbInterfaceDescriptor) descriptor);
+        } else if (descriptor instanceof UsbEndpointDescriptor) {
+            tsReport((UsbEndpointDescriptor) descriptor);
+        } else if (descriptor instanceof UsbHIDDescriptor) {
+            tsReport((UsbHIDDescriptor) descriptor);
+        } else if (descriptor instanceof UsbACAudioControlEndpoint) {
+            tsReport((UsbACAudioControlEndpoint) descriptor);
+        } else if (descriptor instanceof UsbACAudioStreamEndpoint) {
+            tsReport((UsbACAudioStreamEndpoint) descriptor);
+        } else if (descriptor instanceof UsbACHeader) {
+            tsReport((UsbACHeader) descriptor);
+        } else if (descriptor instanceof UsbACFeatureUnit) {
+            tsReport((UsbACFeatureUnit) descriptor);
+        } else if (descriptor instanceof UsbACInputTerminal) {
+            tsReport((UsbACInputTerminal) descriptor);
+        } else if (descriptor instanceof UsbACOutputTerminal) {
+            tsReport((UsbACOutputTerminal) descriptor);
+        } else if (descriptor instanceof UsbACMidiEndpoint) {
+            tsReport((UsbACMidiEndpoint) descriptor);
+        } else if (descriptor instanceof UsbACMixerUnit) {
+            tsReport((UsbACMixerUnit) descriptor);
+        } else if (descriptor instanceof UsbACSelectorUnit) {
+            tsReport((UsbACSelectorUnit) descriptor);
+        } else if (descriptor instanceof UsbASFormatI) {
+            tsReport((UsbASFormatI) descriptor);
+        } else if (descriptor instanceof UsbASFormatII) {
+            tsReport((UsbASFormatII) descriptor);
+        } else if (descriptor instanceof UsbASFormat) {
+            tsReport((UsbASFormat) descriptor);
+        } else if (descriptor instanceof UsbASGeneral) {
+            tsReport((UsbASGeneral) descriptor);
+        } else if (descriptor instanceof UsbInterfaceAssoc) {
+            tsReport((UsbInterfaceAssoc) descriptor);
+        } else if (descriptor instanceof UsbMSMidiHeader) {
+            tsReport((UsbMSMidiHeader) descriptor);
+        } else if (descriptor instanceof UsbMSMidiInputJack) {
+            tsReport((UsbMSMidiInputJack) descriptor);
+        } else if (descriptor instanceof UsbMSMidiOutputJack) {
+            tsReport((UsbMSMidiOutputJack) descriptor);
+        } else if (descriptor instanceof UsbUnknown) {
+            tsReport((UsbUnknown) descriptor);
+        } else if (descriptor instanceof UsbACInterface) {
+            tsReport((UsbACInterface) descriptor);
+        } else if (descriptor instanceof UsbDescriptor) {
+            tsReport((UsbDescriptor) descriptor);
+        }
+    }
+
+    //
+    // Type-specific report() implementations
+    //
+    private void tsReport(UsbDescriptor descriptor) {
+        int length = descriptor.getLength();
+        byte type = descriptor.getType();
+        int status = descriptor.getStatus();
+
+        String descTypeStr = UsbStrings.getDescriptorName(type);
+        writeParagraph(descTypeStr + ":" + type + " l:" + length + " s:" + status);
+    }
+
+    private void tsReport(UsbDeviceDescriptor descriptor) {
+        writeHeader(1, "Device len:" + descriptor.getLength());
+        openList();
+
+        int spec = descriptor.getSpec();
+        writeListItem("spec:" + getBCDString(spec));
+
+        byte devClass = descriptor.getDevClass();
+        String classStr = UsbStrings.getClassName(devClass);
+        byte devSubClass = descriptor.getDevSubClass();
+        String subClasStr = UsbStrings.getClassName(devSubClass);
+        writeListItem("class " + devClass + ":" + classStr + " subclass"
+                + devSubClass + ":" + subClasStr);
+        writeListItem("vendorID:" + descriptor.getVendorID()
+                + " prodID:" + descriptor.getProductID()
+                + " prodRel:" + getBCDString(descriptor.getDeviceRelease()));
+
+        byte mfgIndex = descriptor.getMfgIndex();
+        String manufacturer = UsbDescriptor.getUsbDescriptorString(mConnection, mfgIndex);
+        byte productIndex = descriptor.getProductIndex();
+        String product = UsbDescriptor.getUsbDescriptorString(mConnection, productIndex);
+
+        writeListItem("mfg " + mfgIndex + ":" + manufacturer
+                + " prod " + productIndex + ":" + product);
+        closeList();
+    }
+
+    private void tsReport(UsbConfigDescriptor descriptor) {
+        writeHeader(2, "Config #" + descriptor.getConfigValue()
+                + " len:" + descriptor.getLength());
+
+        openList();
+        writeListItem(descriptor.getNumInterfaces() + " interfaces.");
+        writeListItem("attribs:" + getHexString(descriptor.getAttribs()));
+        closeList();
+    }
+
+    private void tsReport(UsbInterfaceDescriptor descriptor) {
+        byte usbClass = descriptor.getUsbClass();
+        byte usbSubclass = descriptor.getUsbSubclass();
+        String descr = UsbStrings.getDescriptorName(descriptor.getType());
+        String className = UsbStrings.getClassName(usbClass);
+        String subclassName = "";
+        if (usbClass == UsbDescriptor.CLASSID_AUDIO) {
+            subclassName = UsbStrings.getAudioSubclassName(usbSubclass);
+        }
+
+        writeHeader(2, descr + " #" + descriptor.getInterfaceNumber()
+                        + " len:" + descriptor.getLength());
+        String descrStr =
+                UsbDescriptor.getUsbDescriptorString(mConnection, descriptor.getDescrIndex());
+        if (descrStr.length() > 0) {
+            mStringBuilder.append("<br>" + descrStr);
+        }
+        openList();
+        writeListItem("class " + getHexString(usbClass) + ":" + className
+                + " subclass " + getHexString(usbSubclass) + ":" + subclassName);
+        writeListItem(""  + descriptor.getNumEndpoints() + " endpoints");
+        closeList();
+    }
+
+    private void tsReport(UsbEndpointDescriptor descriptor) {
+        writeHeader(3, "Endpoint " + getHexString(descriptor.getType())
+                + " len:" + descriptor.getLength());
+        openList();
+
+        byte address = descriptor.getEndpointAddress();
+        writeListItem("address:"
+                + getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS)
+                + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION)
+                        == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
+
+        byte attributes = descriptor.getAttributes();
+        openListItem();
+        mStringBuilder.append("attribs:" + getHexString(attributes) + " ");
+        switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) {
+            case UsbEndpointDescriptor.TRANSTYPE_CONTROL:
+                mStringBuilder.append("Control");
+                break;
+            case UsbEndpointDescriptor.TRANSTYPE_ISO:
+                mStringBuilder.append("Iso");
+                break;
+            case UsbEndpointDescriptor.TRANSTYPE_BULK:
+                mStringBuilder.append("Bulk");
+                break;
+            case UsbEndpointDescriptor.TRANSTYPE_INTERRUPT:
+                mStringBuilder.append("Interrupt");
+                break;
+        }
+        closeListItem();
+
+        // These flags are only relevant for ISO transfer type
+        if ((attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE)
+                == UsbEndpointDescriptor.TRANSTYPE_ISO) {
+            openListItem();
+            mStringBuilder.append("sync:");
+            switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_SYNCTYPE) {
+                case UsbEndpointDescriptor.SYNCTYPE_NONE:
+                    mStringBuilder.append("NONE");
+                    break;
+                case UsbEndpointDescriptor.SYNCTYPE_ASYNC:
+                    mStringBuilder.append("ASYNC");
+                    break;
+                case UsbEndpointDescriptor.SYNCTYPE_ADAPTSYNC:
+                    mStringBuilder.append("ADAPTIVE ASYNC");
+                    break;
+            }
+            closeListItem();
+
+            openListItem();
+            mStringBuilder.append("useage:");
+            switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_USEAGE) {
+                case UsbEndpointDescriptor.USEAGE_DATA:
+                    mStringBuilder.append("DATA");
+                    break;
+                case UsbEndpointDescriptor.USEAGE_FEEDBACK:
+                    mStringBuilder.append("FEEDBACK");
+                    break;
+                case UsbEndpointDescriptor.USEAGE_EXPLICIT:
+                    mStringBuilder.append("EXPLICIT FEEDBACK");
+                    break;
+                case UsbEndpointDescriptor.USEAGE_RESERVED:
+                    mStringBuilder.append("RESERVED");
+                    break;
+            }
+            closeListItem();
+        }
+        writeListItem("package size:" + descriptor.getPacketSize());
+        writeListItem("interval:" + descriptor.getInterval());
+        closeList();
+    }
+
+    private void tsReport(UsbHIDDescriptor descriptor) {
+        String descr = UsbStrings.getDescriptorName(descriptor.getType());
+        writeHeader(2, descr + " len:" + descriptor.getLength());
+        openList();
+        writeListItem("spec:" + getBCDString(descriptor.getRelease()));
+        writeListItem("type:" + getBCDString(descriptor.getDescriptorType()));
+        writeListItem("descriptor.getNumDescriptors()  descriptors len:"
+                + descriptor.getDescriptorLen());
+        closeList();
+    }
+
+    private void tsReport(UsbACAudioControlEndpoint descriptor) {
+        writeHeader(3, "AC Audio Control Endpoint:" + getHexString(descriptor.getType())
+                + " length:" + descriptor.getLength());
+    }
+
+    private void tsReport(UsbACAudioStreamEndpoint descriptor) {
+        writeHeader(3, "AC Audio Streaming Endpoint:"
+                + getHexString(descriptor.getType())
+                + " length:" + descriptor.getLength());
+    }
+
+    private void tsReport(UsbACHeader descriptor) {
+        tsReport((UsbACInterface) descriptor);
+
+        openList();
+        writeListItem("spec:" + getBCDString(descriptor.getADCRelease()));
+        int numInterfaces = descriptor.getNumInterfaces();
+        writeListItem("" + numInterfaces + " interfaces");
+        if (numInterfaces > 0) {
+            openListItem();
+            mStringBuilder.append("[");
+            byte[] interfaceNums = descriptor.getInterfaceNums();
+            if (numInterfaces != 0 && interfaceNums != null) {
+                for (int index = 0; index < numInterfaces; index++) {
+                    mStringBuilder.append("" + interfaceNums[index]);
+                    if (index < numInterfaces - 1) {
+                        mStringBuilder.append(" ");
+                    }
+                }
+            }
+            mStringBuilder.append("]");
+            closeListItem();
+        }
+        writeListItem("controls:" + getHexString(descriptor.getControls()));
+        closeList();
+    }
+
+    private void tsReport(UsbACFeatureUnit descriptor) {
+        tsReport((UsbACInterface) descriptor);
+    }
+
+    private void tsReport(UsbACInterface descriptor) {
+        String subClassName =
+                descriptor.getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL
+                        ? "AC Control"
+                        : "AC Streaming";
+        byte subtype = descriptor.getSubtype();
+        String subTypeStr = UsbStrings.getACControlInterfaceName(subtype);
+        writeHeader(4, subClassName + " - " + getHexString(subtype)
+                + ":" + subTypeStr + " len:" + descriptor.getLength());
+    }
+
+    private void tsReport(UsbACTerminal descriptor) {
+        tsReport((UsbACInterface) descriptor);
+    }
+
+    private void tsReport(UsbACInputTerminal descriptor) {
+        tsReport((UsbACTerminal) descriptor);
+
+        openList();
+        writeListItem("ID:" + getHexString(descriptor.getTerminalID()));
+        int terminalType = descriptor.getTerminalType();
+        writeListItem("Type:<b>" + getHexString(terminalType) + ":"
+                + UsbStrings.getTerminalName(terminalType) + "</b>");
+        writeListItem("AssocTerminal:" + getHexString(descriptor.getAssocTerminal()));
+        writeListItem("" + descriptor.getNrChannels() + " chans. config:"
+                + getHexString(descriptor.getChannelConfig()));
+        closeList();
+    }
+
+    private void tsReport(UsbACOutputTerminal descriptor) {
+        tsReport((UsbACTerminal) descriptor);
+
+        openList();
+        writeListItem("ID:" + getHexString(descriptor.getTerminalID()));
+        int terminalType = descriptor.getTerminalType();
+        writeListItem("Type:<b>" + getHexString(terminalType) + ":"
+                + UsbStrings.getTerminalName(terminalType) + "</b>");
+        writeListItem("AssocTerminal:" + getHexString(descriptor.getAssocTerminal()));
+        writeListItem("Source:" + getHexString(descriptor.getSourceID()));
+        closeList();
+    }
+
+    private void tsReport(UsbACMidiEndpoint descriptor) {
+        writeHeader(3, "AC Midi Endpoint:" + getHexString(descriptor.getType())
+                + " length:" + descriptor.getLength());
+        openList();
+        writeListItem("" + descriptor.getNumJacks() + " jacks.");
+        closeList();
+    }
+
+    private void tsReport(UsbACMixerUnit descriptor) {
+        tsReport((UsbACInterface) descriptor);
+        openList();
+
+        writeListItem("Unit ID:" + getHexString(descriptor.getUnitID()));
+        byte numInputs = descriptor.getNumInputs();
+        byte[] inputIDs = descriptor.getInputIDs();
+        openListItem();
+        mStringBuilder.append("Num Inputs:" + numInputs + " [");
+        for (int input = 0; input < numInputs; input++) {
+            mStringBuilder.append("" + getHexString(inputIDs[input]));
+            if (input < numInputs - 1) {
+                mStringBuilder.append(" ");
+            }
+        }
+        mStringBuilder.append("]");
+        closeListItem();
+
+        writeListItem("Num Outputs:" + descriptor.getNumOutputs());
+        writeListItem("Chan Config:" + getHexString(descriptor.getChannelConfig()));
+
+        byte[] controls = descriptor.getControls();
+        openListItem();
+        mStringBuilder.append("controls:" + controls.length + " [");
+        for (int ctrl = 0; ctrl < controls.length; ctrl++) {
+            mStringBuilder.append("" + controls[ctrl]);
+            if (ctrl < controls.length - 1) {
+                mStringBuilder.append(" ");
+            }
+        }
+        mStringBuilder.append("]");
+        closeListItem();
+        closeList();
+        // byte mChanNameID; // First channel name string descriptor ID
+        // byte mNameID;       // string descriptor ID of mixer name
+    }
+
+    private void tsReport(UsbACSelectorUnit descriptor) {
+        tsReport((UsbACInterface) descriptor);
+    }
+
+    private void tsReport(UsbASFormat descriptor) {
+        writeHeader(4, "AC Streaming Format "
+                + (descriptor.getFormatType() ==  UsbASFormat.FORMAT_TYPE_I  ? "I" : "II")
+                + " - " + getHexString(descriptor.getSubtype()) + ":"
+                + " len:" + descriptor.getLength());
+    }
+
+    private void tsReport(UsbASFormatI descriptor) {
+        tsReport((UsbASFormat) descriptor);
+        openList();
+        writeListItem("chans:" + descriptor.getNumChannels());
+        writeListItem("subframe size:" + descriptor.getSubframeSize());
+        writeListItem("bit resolution:" + descriptor.getBitResolution());
+        byte sampleFreqType = descriptor.getSampleFreqType();
+        int[] sampleRates = descriptor.getSampleRates();
+        writeListItem("sample freq type:" + sampleFreqType);
+        if (sampleFreqType == 0) {
+            openList();
+            writeListItem("min:" + sampleRates[0]);
+            writeListItem("max:" + sampleRates[1]);
+            closeList();
+        } else {
+            openList();
+            for (int index = 0; index < sampleFreqType; index++) {
+                writeListItem("" + sampleRates[index]);
+            }
+            closeList();
+        }
+        closeList();
+    }
+
+    private void tsReport(UsbASFormatII descriptor) {
+        tsReport((UsbASFormat) descriptor);
+        openList();
+        writeListItem("max bit rate:" + descriptor.getMaxBitRate());
+        writeListItem("samples per frame:" + descriptor.getMaxBitRate());
+        byte sampleFreqType = descriptor.getSamFreqType();
+        int[] sampleRates = descriptor.getSampleRates();
+        writeListItem("sample freq type:" + sampleFreqType);
+        if (sampleFreqType == 0) {
+            openList();
+            writeListItem("min:" + sampleRates[0]);
+            writeListItem("max:" + sampleRates[1]);
+            closeList();
+        } else {
+            openList();
+            for (int index = 0; index < sampleFreqType; index++) {
+                writeListItem("" + sampleRates[index]);
+            }
+            closeList();
+        }
+
+        closeList();
+    }
+
+    private void tsReport(UsbASGeneral descriptor) {
+        tsReport((UsbACInterface) descriptor);
+        openList();
+        int formatTag = descriptor.getFormatTag();
+        writeListItem("fmt:" + UsbStrings.getAudioFormatName(formatTag) + " - "
+                + getHexString(formatTag));
+        closeList();
+    }
+
+    private void tsReport(UsbInterfaceAssoc descriptor) {
+        tsReport((UsbDescriptor) descriptor);
+    }
+
+    private void tsReport(UsbMSMidiHeader descriptor) {
+        writeHeader(3, "MS Midi Header:" + getHexString(descriptor.getType())
+                + " subType:" + getHexString(descriptor.getSubclass())
+                + " length:" + descriptor.getSubclass());
+    }
+
+    private void tsReport(UsbMSMidiInputJack descriptor) {
+        writeHeader(3, "MS Midi Input Jack:" + getHexString(descriptor.getType())
+                + " subType:" + getHexString(descriptor.getSubclass())
+                + " length:" + descriptor.getSubclass());
+    }
+
+    private void tsReport(UsbMSMidiOutputJack descriptor) {
+        writeHeader(3, "MS Midi Output Jack:" + getHexString(descriptor.getType())
+                + " subType:" + getHexString(descriptor.getSubclass())
+                + " length:" + descriptor.getSubclass());
+    }
+
+    private void tsReport(UsbUnknown descriptor) {
+        writeParagraph("<i><b>Unknown Descriptor " + getHexString(descriptor.getType())
+                + " len:" + descriptor.getLength() + "</b></i>");
+        dumpHexArray(descriptor.getRawData(), mStringBuilder);
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/Reporter.java b/services/usb/java/com/android/server/usb/descriptors/report/Reporter.java
new file mode 100644
index 0000000..2944c10
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/report/Reporter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors.report;
+
+import com.android.server.usb.descriptors.UsbDescriptor;
+
+/**
+ * Declares the Reporter interface to provide HTML reporting for UsbDescriptor (sub)classes.
+ *
+ * NOTE: It is the responsibility of the implementor of this interface to correctly
+ * interpret/decode the SPECIFIC UsbDescriptor subclass (perhaps with 'instanceof') that is
+ * passed and handle that in the appropriate manner. This appears to be a
+ * not very object-oriented approach, and that is true. This approach DOES however move the
+ * complexity and 'plumbing' of reporting into the Reporter implementation and avoids needing
+ * a (trivial) type-specific call to 'report()' in each UsbDescriptor (sub)class, instead
+ * having just one in the top-level UsbDescriptor class. It also removes the need to add new
+ * type-specific 'report()' methods to be added to Reporter interface whenever a
+ * new UsbDescriptor subclass is defined. This seems like a pretty good trade-off.
+ *
+ * See HTMLReporter.java in this package for an example of type decoding.
+ */
+public interface Reporter {
+    /**
+     * Generate report for this UsbDescriptor descriptor
+     */
+    void report(UsbDescriptor descriptor);
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/Reporting.java b/services/usb/java/com/android/server/usb/descriptors/report/Reporting.java
new file mode 100644
index 0000000..c13111b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/report/Reporting.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors.report;
+
+/**
+ * Declares the interface for classes that provide reporting functionality.
+ * (This is the double-indirection aspect of the "Visitor" pattern.
+ */
+public interface Reporting {
+    /**
+     * Declares the report method that UsbDescriptor subclasses call.
+     */
+    void report(Reporter reporter);
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java
new file mode 100644
index 0000000..0461150
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors.report;
+
+import com.android.server.usb.descriptors.UsbACInterface;
+import com.android.server.usb.descriptors.UsbDescriptor;
+import com.android.server.usb.descriptors.UsbTerminalTypes;
+
+import java.util.HashMap;
+
+/**
+ * @hide
+ * A class to provide human-readable strings for various USB constants.
+ */
+public class UsbStrings {
+    private static final String TAG = "UsbStrings";
+
+    private static HashMap<Byte, String> sDescriptorNames;
+    private static HashMap<Byte, String> sACControlInterfaceNames;
+    private static HashMap<Byte, String> sACStreamingInterfaceNames;
+    private static HashMap<Byte, String> sClassNames;
+    private static HashMap<Byte, String> sAudioSubclassNames;
+    private static HashMap<Integer, String> sAudioEncodingNames;
+    private static HashMap<Integer, String> sTerminalNames;
+
+    private static void initDescriptorNames() {
+        sDescriptorNames = new HashMap<Byte, String>();
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_DEVICE, "Device");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_CONFIG, "Config");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_STRING, "String");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_INTERFACE, "Interface");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_ENDPOINT, "Endpoint");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_BOS, "BOS (whatever that means)");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_INTERFACEASSOC,
+                "Interface Association");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_CAPABILITY, "Capability");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_HID, "HID");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_REPORT, "Report");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_PHYSICAL, "Physical");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE,
+                "Audio Class Interface");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT, "Audio Class Endpoint");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_HUB, "Hub");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_SUPERSPEED_HUB, "Superspeed Hub");
+        sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_ENDPOINT_COMPANION,
+                "Endpoint Companion");
+    }
+
+    private static void initACControlInterfaceNames() {
+        sACControlInterfaceNames = new HashMap<Byte, String>();
+        sACControlInterfaceNames.put(UsbACInterface.ACI_UNDEFINED, "Undefined");
+        sACControlInterfaceNames.put(UsbACInterface.ACI_HEADER, "Header");
+        sACControlInterfaceNames.put(UsbACInterface.ACI_INPUT_TERMINAL, "Input Terminal");
+        sACControlInterfaceNames.put(UsbACInterface.ACI_OUTPUT_TERMINAL, "Output Terminal");
+        sACControlInterfaceNames.put(UsbACInterface.ACI_MIXER_UNIT, "Mixer Unit");
+        sACControlInterfaceNames.put(UsbACInterface.ACI_SELECTOR_UNIT, "Selector Unit");
+        sACControlInterfaceNames.put(UsbACInterface.ACI_FEATURE_UNIT, "Feature Unit");
+        sACControlInterfaceNames.put(UsbACInterface.ACI_PROCESSING_UNIT, "Processing Unit");
+        sACControlInterfaceNames.put(UsbACInterface.ACI_EXTENSION_UNIT, "Extension Unit");
+    }
+
+    private static void initACStreamingInterfaceNames() {
+        sACStreamingInterfaceNames = new HashMap<Byte, String>();
+        sACStreamingInterfaceNames.put(UsbACInterface.ASI_UNDEFINED, "Undefined");
+        sACStreamingInterfaceNames.put(UsbACInterface.ASI_GENERAL, "General");
+        sACStreamingInterfaceNames.put(UsbACInterface.ASI_FORMAT_TYPE, "Format Type");
+        sACStreamingInterfaceNames.put(UsbACInterface.ASI_FORMAT_SPECIFIC, "Format Specific");
+    }
+
+    private static void initClassNames() {
+        sClassNames = new HashMap<Byte, String>();
+        sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device");
+        sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio");
+        sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications");
+        sClassNames.put(UsbDescriptor.CLASSID_HID, "HID");
+        sClassNames.put(UsbDescriptor.CLASSID_PHYSICAL, "Physical");
+        sClassNames.put(UsbDescriptor.CLASSID_IMAGE, "Image");
+        sClassNames.put(UsbDescriptor.CLASSID_PRINTER, "Printer");
+        sClassNames.put(UsbDescriptor.CLASSID_STORAGE, "Storage");
+        sClassNames.put(UsbDescriptor.CLASSID_HUB, "Hub");
+        sClassNames.put(UsbDescriptor.CLASSID_CDC_CONTROL, "CDC Control");
+        sClassNames.put(UsbDescriptor.CLASSID_SMART_CARD, "Smart Card");
+        sClassNames.put(UsbDescriptor.CLASSID_SECURITY, "Security");
+        sClassNames.put(UsbDescriptor.CLASSID_VIDEO, "Video");
+        sClassNames.put(UsbDescriptor.CLASSID_HEALTHCARE, "Healthcare");
+        sClassNames.put(UsbDescriptor.CLASSID_AUDIOVIDEO, "Audio/Video");
+        sClassNames.put(UsbDescriptor.CLASSID_BILLBOARD, "Billboard");
+        sClassNames.put(UsbDescriptor.CLASSID_TYPECBRIDGE, "Type C Bridge");
+        sClassNames.put(UsbDescriptor.CLASSID_DIAGNOSTIC, "Diagnostic");
+        sClassNames.put(UsbDescriptor.CLASSID_WIRELESS, "Wireless");
+        sClassNames.put(UsbDescriptor.CLASSID_MISC, "Misc");
+        sClassNames.put(UsbDescriptor.CLASSID_APPSPECIFIC, "Application Specific");
+        sClassNames.put(UsbDescriptor.CLASSID_VENDSPECIFIC, "Vendor Specific");
+    }
+
+    private static void initAudioSubclassNames() {
+        sAudioSubclassNames = new HashMap<Byte, String>();
+        sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded");
+        sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control");
+        sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming");
+        sAudioSubclassNames.put(UsbDescriptor.AUDIO_MIDISTREAMING, "MIDI Streaming");
+    }
+
+    private static void initAudioEncodingNames() {
+        sAudioEncodingNames = new HashMap<Integer, String>();
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_I_UNDEFINED, "Format I Undefined");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_I_PCM, "Format I PCM");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_I_PCM8, "Format I PCM8");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_I_IEEE_FLOAT, "Format I FLOAT");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_I_ALAW, "Format I ALAW");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_I_MULAW, "Format I MuLAW");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_II_UNDEFINED, "FORMAT_II Undefined");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_II_MPEG, "FORMAT_II MPEG");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_II_AC3, "FORMAT_II AC3");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_III_UNDEFINED, "FORMAT_III Undefined");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937AC3, "FORMAT_III IEC1937 AC3");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937_MPEG1_Layer1,
+                "FORMAT_III MPEG1 Layer 1");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937_MPEG1_Layer2,
+                "FORMAT_III MPEG1 Layer 2");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937_MPEG2_EXT,
+                "FORMAT_III MPEG2 EXT");
+        sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937_MPEG2_Layer1LS,
+                "FORMAT_III MPEG2 Layer1LS");
+    }
+
+    private static void initTerminalNames() {
+        sTerminalNames = new HashMap<Integer, String>();
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_USB_STREAMING, "USB Streaming");
+
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_UNDEFINED, "Undefined");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_MIC, "Microphone");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_DESKTOP_MIC, "Desktop Microphone");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_PERSONAL_MIC,
+                "Personal (headset) Microphone");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_OMNI_MIC, "Omni Microphone");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_MIC_ARRAY, "Microphone Array");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_PROC_MIC_ARRAY,
+                "Proecessing Microphone Array");
+
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_UNDEFINED, "Undefined");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_SPEAKER, "Speaker");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_HEADPHONES, "Headphones");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_HEADMOUNTED, "Head Mounted Speaker");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_DESKTOPSPEAKER, "Desktop Speaker");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_ROOMSPEAKER, "Room Speaker");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_COMSPEAKER, "Communications Speaker");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_LFSPEAKER, "Low Frequency Speaker");
+
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED, "Undefined");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_HANDSET, "Handset");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_HEADSET, "Headset");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_SKRPHONE, "Speaker Phone");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_SKRPHONE_SUPRESS,
+                "Speaker Phone (echo supressing)");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_SKRPHONE_CANCEL,
+                "Speaker Phone (echo canceling)");
+
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_TELE_UNDEFINED, "Undefined");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_TELE_PHONELINE, "Phone Line");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_TELE_PHONE, "Telephone");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_TELE_DOWNLINEPHONE, "Down Line Phone");
+
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_UNDEFINED, "Undefined");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_ANALOG, "Analog Connector");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL, "Digital Connector");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_LINE, "Line Connector");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_LEGACY, "Legacy Audio Connector");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_SPIDF, "S/PIDF Interface");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_1394DA, "1394 Audio");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_1394DV, "1394 Audio/Video");
+
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_UNDEFINED, "Undefined");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_CALNOISE, "Calibration Nose");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_EQNOISE, "EQ Noise");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_CDPLAYER, "CD Player");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_DAT, "DAT");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_DCC, "DCC");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_MINIDISK, "Mini Disk");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_ANALOGTAPE, "Analog Tap");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_PHONOGRAPH, "Phonograph");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_VCRAUDIO, "VCR Audio");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_VIDDISKAUDIO, "Video Disk Audio");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_DVDAUDIO, "DVD Audio");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_TVAUDIO, "TV Audio");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_SATELLITEAUDIO, "Satellite Audio");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_CABLEAUDIO, "Cable Tuner Audio");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_DSSAUDIO, "DSS Audio");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_RADIOTRANSMITTER, "Radio Transmitter");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_MULTITRACK, "Multitrack Recorder");
+        sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_SYNTHESIZER, "Synthesizer");
+    }
+
+    /**
+     * Retrieves the terminal name for the specified terminal type ID.
+     */
+    public static String getTerminalName(int terminalType) {
+        String name = sTerminalNames.get(terminalType);
+        return name != null
+                ? name
+                : "Unknown Terminal Type 0x" + Integer.toHexString(terminalType);
+    }
+    /**
+     * Initializes string tables.
+     */
+    public static void allocUsbStrings() {
+        initDescriptorNames();
+        initACControlInterfaceNames();
+        initACStreamingInterfaceNames();
+        initClassNames();
+        initAudioSubclassNames();
+        initAudioEncodingNames();
+        initTerminalNames();
+    }
+
+    /**
+     * Initializes string tables.
+     */
+    public static void releaseUsbStrings() {
+        sDescriptorNames = null;
+        sACControlInterfaceNames = null;
+        sACStreamingInterfaceNames = null;
+        sClassNames = null;
+        sAudioSubclassNames = null;
+        sAudioEncodingNames = null;
+        sTerminalNames = null;
+    }
+
+    /**
+     * Retrieves the name for the specified descriptor ID.
+     */
+    public static String getDescriptorName(byte descriptorID) {
+        String name = sDescriptorNames.get(descriptorID);
+        int iDescriptorID = descriptorID & 0xFF;
+        return name != null
+            ? name
+            : "Unknown Descriptor [0x" + Integer.toHexString(iDescriptorID)
+                + ":" + iDescriptorID + "]";
+    }
+
+    /**
+     * Retrieves the audio-class control interface name for the specified audio-class subtype.
+     */
+    public static String getACControlInterfaceName(byte subtype) {
+        String name = sACControlInterfaceNames.get(subtype);
+        int iSubType = subtype & 0xFF;
+        return name != null
+                ? name
+                : "Unknown subtype [0x" + Integer.toHexString(iSubType)
+                    + ":" + iSubType + "]";
+    }
+
+    /**
+     * Retrieves the audio-class streaming interface name for the specified audio-class subtype.
+     */
+    public static String getACStreamingInterfaceName(byte subtype) {
+        String name = sACStreamingInterfaceNames.get(subtype);
+        int iSubType = subtype & 0xFF;
+        return name != null
+                ? name
+                : "Unknown Subtype [0x" + Integer.toHexString(iSubType) + ":"
+                    + iSubType + "]";
+    }
+
+    /**
+     * Retrieves the name for the specified USB class ID.
+     */
+    public static String getClassName(byte classID) {
+        String name = sClassNames.get(classID);
+        int iClassID = classID & 0xFF;
+        return name != null
+                ? name
+                : "Unknown Class ID [0x" + Integer.toHexString(iClassID) + ":"
+                    + iClassID + "]";
+    }
+
+    /**
+     * Retrieves the name for the specified USB audio subclass ID.
+     */
+    public static String getAudioSubclassName(byte subClassID) {
+        String name = sAudioSubclassNames.get(subClassID);
+        int iSubclassID = subClassID & 0xFF;
+        return name != null
+                ? name
+                : "Unknown Audio Subclass [0x" + Integer.toHexString(iSubclassID) + ":"
+                    + iSubclassID + "]";
+    }
+
+    /**
+     * Retrieves the name for the specified USB audio format ID.
+     */
+    public static String getAudioFormatName(int formatID) {
+        String name = sAudioEncodingNames.get(formatID);
+        return name != null
+                ? name
+                : "Unknown Format (encoding) ID [0x" + Integer.toHexString(formatID) + ":"
+                    + formatID + "]";
+    }
+}