diff options
39 files changed, 3842 insertions, 12 deletions
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index a3d28bbd3754..e2a82b74e283 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -33,6 +33,7 @@ LOCAL_SRC_FILES += \ $(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 000000000000..98c5ec1bd4d5 --- /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 d315b181c357..68c1d5f6ec7f 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 @@ public final class UsbAlsaManager { 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 @@ public final class UsbAlsaManager { 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 @@ public final class UsbAlsaManager { // 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 @@ public final class UsbAlsaManager { 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 af78c05ae399..40aadbbcc457 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 android.util.Slog; 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 @@ public class UsbHostManager { 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 000000000000..d678931fd01a --- /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 000000000000..96fcc6a0b8db --- /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 000000000000..d387883d3049 --- /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 000000000000..223496ab016e --- /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 000000000000..739fe5503a1d --- /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 000000000000..e31438c58e06 --- /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 000000000000..653a7de5457e --- /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 000000000000..0ab7fccd2c3b --- /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 000000000000..9c072426cc49 --- /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 000000000000..552b5ae308d6 --- /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 000000000000..f957e3dbe217 --- /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 000000000000..b1f60bdcf6ed --- /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 000000000000..ea80208ee3f3 --- /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 000000000000..d7c84c6a0965 --- /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 000000000000..347a6cffb525 --- /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 000000000000..abdc62145aa2 --- /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 000000000000..c4f42d318213 --- /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 000000000000..185cee20b090 --- /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 000000000000..8ae6d0f1ee7e --- /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 000000000000..63b2d7f6aed7 --- /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 000000000000..7c074dadadf9 --- /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 000000000000..90848caba852 --- /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 000000000000..def670093e6e --- /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 000000000000..56c07ec9a071 --- /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 000000000000..4b18a01b1c8b --- /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 000000000000..21b5e0cbaa1b --- /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 000000000000..4452b23cb6ae --- /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 000000000000..2d33ba7727dd --- /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 000000000000..bd2dc11d57df --- /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 000000000000..b5214625126a --- /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 000000000000..a6fe8bba3508 --- /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 000000000000..c98789d880a0 --- /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 000000000000..2944c10796f6 --- /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 000000000000..c13111b3e81c --- /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 000000000000..0461150abd27 --- /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 + "]"; + } +} |