summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt13
-rw-r--r--media/java/android/media/midi/IMidiManager.aidl4
-rw-r--r--media/java/android/media/midi/MidiDeviceInfo.java140
-rw-r--r--media/java/android/media/midi/MidiManager.java170
-rw-r--r--media/java/android/media/midi/package.html41
-rw-r--r--media/native/midi/MidiDeviceInfo.cpp5
-rw-r--r--media/native/midi/MidiDeviceInfo.h14
-rw-r--r--media/native/midi/amidi.cpp8
-rw-r--r--media/native/midi/amidi_internal.h1
-rw-r--r--media/native/midi/include/amidi/AMidi.h96
-rw-r--r--media/native/midi/libamidi.map.txt1
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java3
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java39
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaManager.java5
-rw-r--r--services/usb/java/com/android/server/usb/UsbHostManager.java31
-rw-r--r--services/usb/java/com/android/server/usb/UsbMidiDevice.java3
-rw-r--r--services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java469
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java4
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java4
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java26
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java (renamed from services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java)22
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java67
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java115
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java5
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java15
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java11
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java173
27 files changed, 1395 insertions, 90 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index fd429f037895..14c8bb4a18f5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -25339,6 +25339,7 @@ package android.media.midi {
public final class MidiDeviceInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getDefaultProtocol();
method public int getId();
method public int getInputPortCount();
method public int getOutputPortCount();
@@ -25355,6 +25356,14 @@ package android.media.midi {
field public static final String PROPERTY_SERIAL_NUMBER = "serial_number";
field public static final String PROPERTY_USB_DEVICE = "usb_device";
field public static final String PROPERTY_VERSION = "version";
+ field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3; // 0x3
+ field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4; // 0x4
+ field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1; // 0x1
+ field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2; // 0x2
+ field public static final int PROTOCOL_UMP_MIDI_2_0 = 17; // 0x11
+ field public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18; // 0x12
+ field public static final int PROTOCOL_UMP_USE_MIDI_CI = 0; // 0x0
+ field public static final int PROTOCOL_UNKNOWN = -1; // 0xffffffff
field public static final int TYPE_BLUETOOTH = 3; // 0x3
field public static final int TYPE_USB = 1; // 0x1
field public static final int TYPE_VIRTUAL = 2; // 0x2
@@ -25396,10 +25405,14 @@ package android.media.midi {
public final class MidiManager {
method public android.media.midi.MidiDeviceInfo[] getDevices();
+ method @NonNull public java.util.Collection<android.media.midi.MidiDeviceInfo> getDevicesForTransport(int);
method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler);
method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler);
method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
+ method public void registerDeviceCallbackForTransport(@NonNull android.media.midi.MidiManager.DeviceCallback, @Nullable android.os.Handler, int);
method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
+ field public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; // 0x1
+ field public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; // 0x2
}
public static class MidiManager.DeviceCallback {
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index d3c8e0adb3ce..b03f78504635 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -31,6 +31,8 @@ interface IMidiManager
{
MidiDeviceInfo[] getDevices();
+ MidiDeviceInfo[] getDevicesForTransport(int transport);
+
// for device creation & removal notifications
void registerListener(IBinder clientToken, in IMidiDeviceListener listener);
void unregisterListener(IBinder clientToken, in IMidiDeviceListener listener);
@@ -43,7 +45,7 @@ interface IMidiManager
// for registering built-in MIDI devices
MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
int numOutputPorts, in String[] inputPortNames, in String[] outputPortNames,
- in Bundle properties, int type);
+ in Bundle properties, int type, int defaultProtocol);
// for unregistering built-in MIDI devices
void unregisterDeviceServer(in IMidiDeviceServer server);
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index dd3b6dbd6a39..b888fb00df9b 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -16,11 +16,15 @@
package android.media.midi;
+import android.annotation.IntDef;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* This class contains information to describe a MIDI device.
* For now we only have information that can be retrieved easily for USB devices,
@@ -54,6 +58,110 @@ public final class MidiDeviceInfo implements Parcelable {
public static final int TYPE_BLUETOOTH = 3;
/**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use UMP to negotiate with the device with MIDI-CI.
+ * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
+ * Call {@link MidiManager#getDevicesForTransport} with parameter
+ * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+ * @see MidiDeviceInfo#getDefaultProtocol
+ */
+ public static final int PROTOCOL_UMP_USE_MIDI_CI = 0;
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 1.0 through UMP with packet sizes up to 64 bits.
+ * Call {@link MidiManager#getDevicesForTransport} with parameter
+ * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+ * @see MidiDeviceInfo#getDefaultProtocol
+ */
+ public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1;
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 1.0 through UMP with packet sizes up to 64 bits and jitter reduction timestamps.
+ * Call {@link MidiManager#getDevicesForTransport} with parameter
+ * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+ * @see MidiDeviceInfo#getDefaultProtocol
+ */
+ public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2;
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 1.0 through UMP with packet sizes up to 128 bits.
+ * Call {@link MidiManager#getDevicesForTransport} with parameter
+ * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+ * @see MidiDeviceInfo#getDefaultProtocol
+ */
+ public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3;
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 1.0 through UMP with packet sizes up to 128 bits and jitter reduction timestamps.
+ * Call {@link MidiManager#getDevicesForTransport} with parameter
+ * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+ * @see MidiDeviceInfo#getDefaultProtocol
+ */
+ public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4;
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 2.0 through UMP.
+ * Call {@link MidiManager#getDevicesForTransport} with parameter
+ * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+ * @see MidiDeviceInfo#getDefaultProtocol
+ */
+ public static final int PROTOCOL_UMP_MIDI_2_0 = 17;
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 2.0 through UMP and jitter reduction timestamps.
+ * Call {@link MidiManager#getDevicesForTransport} with parameter
+ * {@link MidiManager#TRANSPORT_UNIVERSAL_MIDI_PACKETS} to get devices with this transport.
+ * @see MidiDeviceInfo#getDefaultProtocol
+ */
+ public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18;
+
+ /**
+ * Constant representing a device with an unknown default protocol.
+ * If Universal MIDI Packets (UMP) are needed, use MIDI-CI through MIDI 1.0.
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
+ * @see MidiDeviceInfo#getDefaultProtocol
+ */
+ public static final int PROTOCOL_UNKNOWN = -1;
+
+ /**
+ * @see MidiDeviceInfo#getDefaultProtocol
+ * @hide
+ */
+ @IntDef(prefix = { "PROTOCOL_" }, value = {
+ PROTOCOL_UMP_USE_MIDI_CI,
+ PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS,
+ PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS,
+ PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS,
+ PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS,
+ PROTOCOL_UMP_MIDI_2_0,
+ PROTOCOL_UMP_MIDI_2_0_AND_JRTS,
+ PROTOCOL_UNKNOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DefaultProtocol {}
+
+ /**
* Bundle key for the device's user visible name property.
* The value for this property is of type {@link java.lang.String}.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}.
@@ -196,6 +304,7 @@ public final class MidiDeviceInfo implements Parcelable {
private final String[] mOutputPortNames;
private final Bundle mProperties;
private final boolean mIsPrivate;
+ private final int mDefaultProtocol;
/**
* MidiDeviceInfo should only be instantiated by MidiService implementation
@@ -203,7 +312,7 @@ public final class MidiDeviceInfo implements Parcelable {
*/
public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
String[] inputPortNames, String[] outputPortNames, Bundle properties,
- boolean isPrivate) {
+ boolean isPrivate, int defaultProtocol) {
// Check num ports for out-of-range values. Typical values will be
// between zero and three. More than 16 would be very unlikely
// because the port index field in the USB packet is only 4 bits.
@@ -234,6 +343,7 @@ public final class MidiDeviceInfo implements Parcelable {
}
mProperties = properties;
mIsPrivate = isPrivate;
+ mDefaultProtocol = defaultProtocol;
}
/**
@@ -312,6 +422,18 @@ public final class MidiDeviceInfo implements Parcelable {
return mIsPrivate;
}
+ /**
+ * Returns the default protocol. For most devices, this will be {@link #PROTOCOL_UNKNOWN}.
+ * Returning {@link #PROTOCOL_UNKNOWN} is not an error; the device just doesn't support
+ * Universal MIDI Packets by default.
+ *
+ * @return the device's default protocol.
+ */
+ @DefaultProtocol
+ public int getDefaultProtocol() {
+ return mDefaultProtocol;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof MidiDeviceInfo) {
@@ -331,11 +453,12 @@ public final class MidiDeviceInfo implements Parcelable {
// This is a hack to force the mProperties Bundle to unparcel so we can
// print all the names and values.
mProperties.getString(PROPERTY_NAME);
- return ("MidiDeviceInfo[mType=" + mType +
- ",mInputPortCount=" + mInputPortCount +
- ",mOutputPortCount=" + mOutputPortCount +
- ",mProperties=" + mProperties +
- ",mIsPrivate=" + mIsPrivate);
+ return ("MidiDeviceInfo[mType=" + mType
+ + ",mInputPortCount=" + mInputPortCount
+ + ",mOutputPortCount=" + mOutputPortCount
+ + ",mProperties=" + mProperties
+ + ",mIsPrivate=" + mIsPrivate
+ + ",mDefaultProtocol=" + mDefaultProtocol);
}
public static final @android.annotation.NonNull Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -349,10 +472,12 @@ public final class MidiDeviceInfo implements Parcelable {
String[] inputPortNames = in.createStringArray();
String[] outputPortNames = in.createStringArray();
boolean isPrivate = (in.readInt() == 1);
+ int defaultProtocol = in.readInt();
Bundle basicPropertiesIgnored = in.readBundle();
Bundle properties = in.readBundle();
return new MidiDeviceInfo(type, id, inputPortCount, outputPortCount,
- inputPortNames, outputPortNames, properties, isPrivate);
+ inputPortNames, outputPortNames, properties, isPrivate,
+ defaultProtocol);
}
public MidiDeviceInfo[] newArray(int size) {
@@ -390,6 +515,7 @@ public final class MidiDeviceInfo implements Parcelable {
parcel.writeStringArray(mInputPortNames);
parcel.writeStringArray(mOutputPortNames);
parcel.writeInt(mIsPrivate ? 1 : 0);
+ parcel.writeInt(mDefaultProtocol);
// "Basic" properties only contain properties of primitive types
// and thus can be read back by native code. "Extra" properties is
// a superset that contains all properties.
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index dee94c681e87..a049a8891f48 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -16,18 +16,25 @@
package android.media.midi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.SystemService;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
-import android.os.IBinder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -39,6 +46,39 @@ public final class MidiManager {
private static final String TAG = "MidiManager";
/**
+ * Constant representing MIDI devices.
+ * These devices do NOT support Universal MIDI Packets by default.
+ * These support the original MIDI 1.0 byte stream.
+ * When communicating to a USB device, a raw byte stream will be padded for USB.
+ * Likewise, for a Bluetooth device, the raw bytes will be converted for Bluetooth.
+ * For virtual devices, the byte stream will be passed directly.
+ * If Universal MIDI Packets are needed, please use MIDI-CI.
+ * @see MidiManager#getDevicesForTransport
+ */
+ public static final int TRANSPORT_MIDI_BYTE_STREAM = 1;
+
+ /**
+ * Constant representing Universal MIDI devices.
+ * These devices do support Universal MIDI Packets (UMP) by default.
+ * When sending data to these devices, please send UMP.
+ * Packets should always be a multiple of 4 bytes.
+ * UMP is defined in the USB MIDI 2.0 spec. Please read the standard for more info.
+ * @see MidiManager#getDevicesForTransport
+ */
+ public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2;
+
+ /**
+ * @see MidiManager#getDevicesForTransport
+ * @hide
+ */
+ @IntDef(prefix = { "TRANSPORT_" }, value = {
+ TRANSPORT_MIDI_BYTE_STREAM,
+ TRANSPORT_UNIVERSAL_MIDI_PACKETS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Transport {}
+
+ /**
* Intent for starting BluetoothMidiService
* @hide
*/
@@ -68,37 +108,43 @@ public final class MidiManager {
private class DeviceListener extends IMidiDeviceListener.Stub {
private final DeviceCallback mCallback;
private final Handler mHandler;
+ private final int mTransport;
- public DeviceListener(DeviceCallback callback, Handler handler) {
+ DeviceListener(DeviceCallback callback, Handler handler, int transport) {
mCallback = callback;
mHandler = handler;
+ mTransport = transport;
}
@Override
public void onDeviceAdded(MidiDeviceInfo device) {
- if (mHandler != null) {
- final MidiDeviceInfo deviceF = device;
- mHandler.post(new Runnable() {
- @Override public void run() {
- mCallback.onDeviceAdded(deviceF);
- }
- });
- } else {
- mCallback.onDeviceAdded(device);
+ if (shouldInvokeCallback(device)) {
+ if (mHandler != null) {
+ final MidiDeviceInfo deviceF = device;
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ mCallback.onDeviceAdded(deviceF);
+ }
+ });
+ } else {
+ mCallback.onDeviceAdded(device);
+ }
}
}
@Override
public void onDeviceRemoved(MidiDeviceInfo device) {
- if (mHandler != null) {
- final MidiDeviceInfo deviceF = device;
- mHandler.post(new Runnable() {
- @Override public void run() {
- mCallback.onDeviceRemoved(deviceF);
- }
- });
- } else {
- mCallback.onDeviceRemoved(device);
+ if (shouldInvokeCallback(device)) {
+ if (mHandler != null) {
+ final MidiDeviceInfo deviceF = device;
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ mCallback.onDeviceRemoved(deviceF);
+ }
+ });
+ } else {
+ mCallback.onDeviceRemoved(device);
+ }
}
}
@@ -115,6 +161,25 @@ public final class MidiManager {
mCallback.onDeviceStatusChanged(status);
}
}
+
+ /**
+ * Used to figure out whether callbacks should be invoked. Only invoke callbacks of
+ * the correct type.
+ *
+ * @param MidiDeviceInfo the device to check
+ * @return whether to invoke a callback
+ */
+ private boolean shouldInvokeCallback(MidiDeviceInfo device) {
+ // UMP devices have protocols that are not PROTOCOL_UNKNOWN
+ if (mTransport == TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
+ return (device.getDefaultProtocol() != MidiDeviceInfo.PROTOCOL_UNKNOWN);
+ } else if (mTransport == TRANSPORT_MIDI_BYTE_STREAM) {
+ return (device.getDefaultProtocol() == MidiDeviceInfo.PROTOCOL_UNKNOWN);
+ } else {
+ Log.e(TAG, "Invalid transport type: " + mTransport);
+ return false;
+ }
+ }
}
/**
@@ -167,8 +232,10 @@ public final class MidiManager {
}
/**
- * Registers a callback to receive notifications when MIDI devices are added and removed.
- *
+ * Registers a callback to receive notifications when MIDI 1.0 devices are added and removed.
+ * These are devices that do not default to Universal MIDI Packets. To register for a callback
+ * for those, call {@link #registerDeviceCallbackForTransport} instead.
+
* The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately
* for any devices that have open ports. This allows applications to know which input
* ports are already in use and, therefore, unavailable.
@@ -182,7 +249,30 @@ public final class MidiManager {
* callback is unspecified.
*/
public void registerDeviceCallback(DeviceCallback callback, Handler handler) {
- DeviceListener deviceListener = new DeviceListener(callback, handler);
+ registerDeviceCallbackForTransport(callback, handler, TRANSPORT_MIDI_BYTE_STREAM);
+ }
+
+ /**
+ * Registers a callback to receive notifications when MIDI devices are added and removed
+ * for a specific transport type.
+ *
+ * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately
+ * for any devices that have open ports. This allows applications to know which input
+ * ports are already in use and, therefore, unavailable.
+ *
+ * Applications should call {@link #getDevicesForTransport} before registering the callback
+ * to get a list of devices already added.
+ *
+ * @param callback a {@link DeviceCallback} for MIDI device notifications
+ * @param handler The {@link android.os.Handler Handler} that will be used for delivering the
+ * device notifications. If handler is null, then the thread used for the
+ * callback is unspecified.
+ * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or
+ * TRANSPORT_UNIVERSAL_MIDI_PACKETS.
+ */
+ public void registerDeviceCallbackForTransport(@NonNull DeviceCallback callback,
+ @Nullable Handler handler, @Transport int transport) {
+ DeviceListener deviceListener = new DeviceListener(callback, handler, transport);
try {
mService.registerListener(mToken, deviceListener);
} catch (RemoteException e) {
@@ -208,9 +298,11 @@ public final class MidiManager {
}
/**
- * Gets the list of all connected MIDI devices.
+ * Gets a list of connected MIDI devices. This returns all devices that do
+ * not default to Universal MIDI Packets. To get those instead, please call
+ * {@link #getDevicesForTransport} instead.
*
- * @return an array of all MIDI devices
+ * @return an array of MIDI devices
*/
public MidiDeviceInfo[] getDevices() {
try {
@@ -220,6 +312,29 @@ public final class MidiManager {
}
}
+ /**
+ * Gets a list of connected MIDI devices by transport. TRANSPORT_MIDI_BYTE_STREAM
+ * is used for MIDI 1.0 and is the most common.
+ * For devices with built in Universal MIDI Packet support, use
+ * TRANSPORT_UNIVERSAL_MIDI_PACKETS instead.
+ *
+ * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or
+ * TRANSPORT_UNIVERSAL_MIDI_PACKETS.
+ * @return a collection of MIDI devices
+ */
+ public @NonNull Collection<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) {
+ try {
+ MidiDeviceInfo[] devices = mService.getDevicesForTransport(transport);
+ Collection<MidiDeviceInfo> out = new ArrayList<MidiDeviceInfo>(devices.length);
+ for (int i = 0; i < devices.length; i++) {
+ out.add(devices[i]);
+ }
+ return out;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private void sendOpenDeviceResponse(final MidiDevice device,
final OnDeviceOpenedListener listener, Handler handler) {
if (handler != null) {
@@ -311,13 +426,14 @@ public final class MidiManager {
/** @hide */
public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
- Bundle properties, int type, MidiDeviceServer.Callback callback) {
+ Bundle properties, int type, int defaultProtocol,
+ MidiDeviceServer.Callback callback) {
try {
MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
numOutputPorts, callback);
MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames,
- properties, type);
+ properties, type, defaultProtocol);
if (deviceInfo == null) {
Log.e(TAG, "registerVirtualDevice failed");
return null;
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 33c54900cd07..67df1b2fa315 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -405,5 +405,46 @@ apps using the
<a href="#get_list_of_already_plugged_in_entities">MIDI device discovery calls described above</a>.
</p>
+<h1 id=using_midi_2_0_over_usb>Using MIDI 2.0 over USB</h1>
+
+<p>An app can use MIDI 2.0 over USB starting in Android T. MIDI 2.0 packets are embedded in
+Universal MIDI Packets, or UMP for short. A MIDI 2.0 USB device should create two interfaces,
+one endpoint that accepts only MIDI 1.0 packets and one that accepts only UMP packets.
+For more info about MIDI 2.0 and UMP, please read the MIDI 2.0 USB spec.</p>
+
+<p>MidiManager.getDevices() would simply return the 1.0 interface. This interface should work
+exactly the same as before. In order to use the new UMP interface, retrieve the device with the
+following code snippet.</p>
+
+<pre class=prettyprint>
+Collection&#60;MidiDeviceInfo&#62; universalDeviceInfos = midiManager.getDevicesForTransport(
+ MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
+</pre>
+
+<p>UMP Packets are always in multiple of 4 bytes. For each set of 4 bytes, they are sent in network
+order. Compare the following NoteOn code snippet with the NoteOn code snippet above. </p>
+
+<pre class=prettyprint>
+byte[] buffer = new byte[32];
+int numBytes = 0;
+int channel = 3; // MIDI channels 1-16 are encoded as 0-15.
+int group = 0;
+buffer[numBytes++] = (byte)(0x20 + group); // MIDI 1.0 voice message
+buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on
+buffer[numBytes++] = (byte)60; // pitch is middle C
+buffer[numBytes++] = (byte)127; // max velocity
+int offset = 0;
+// post is non-blocking
+inputPort.send(buffer, offset, numBytes);
+</pre>
+
+<p>MIDI 2.0 messages can be sent through UMP after negotiating with the device. This is called
+MIDI-CI and is documented in the MIDI 2.0 spec. Some USB devices support pre-negotiated MIDI 2.0.
+For a MidiDeviceInfo, you can query the defaultProtocol.</p>
+
+<pre class=prettyprint>
+int defaultProtocol = info.getDefaultProtocol();
+</pre>
+
</body>
</html>
diff --git a/media/native/midi/MidiDeviceInfo.cpp b/media/native/midi/MidiDeviceInfo.cpp
index 8a573fba322b..14524883470f 100644
--- a/media/native/midi/MidiDeviceInfo.cpp
+++ b/media/native/midi/MidiDeviceInfo.cpp
@@ -64,6 +64,7 @@ status_t MidiDeviceInfo::writeToParcel(Parcel* parcel) const {
RETURN_IF_FAILED(writeStringVector(parcel, mInputPortNames));
RETURN_IF_FAILED(writeStringVector(parcel, mOutputPortNames));
RETURN_IF_FAILED(parcel->writeInt32(mIsPrivate ? 1 : 0));
+ RETURN_IF_FAILED(parcel->writeInt32(mDefaultProtocol));
RETURN_IF_FAILED(mProperties.writeToParcel(parcel));
// This corresponds to "extra" properties written by Java code
RETURN_IF_FAILED(mProperties.writeToParcel(parcel));
@@ -83,6 +84,7 @@ status_t MidiDeviceInfo::readFromParcel(const Parcel* parcel) {
int32_t isPrivate;
RETURN_IF_FAILED(parcel->readInt32(&isPrivate));
mIsPrivate = isPrivate == 1;
+ RETURN_IF_FAILED(parcel->readInt32(&mDefaultProtocol));
RETURN_IF_FAILED(mProperties.readFromParcel(parcel));
// Ignore "extra" properties as they may contain Java Parcelables
return OK;
@@ -130,7 +132,8 @@ bool operator==(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs) {
areVectorsEqual(lhs.mInputPortNames, rhs.mInputPortNames) &&
areVectorsEqual(lhs.mOutputPortNames, rhs.mOutputPortNames) &&
lhs.mProperties == rhs.mProperties &&
- lhs.mIsPrivate == rhs.mIsPrivate);
+ lhs.mIsPrivate == rhs.mIsPrivate &&
+ lhs.mDefaultProtocol == rhs.mDefaultProtocol);
}
} // namespace midi
diff --git a/media/native/midi/MidiDeviceInfo.h b/media/native/midi/MidiDeviceInfo.h
index 5b4a241323d7..23e1cb474168 100644
--- a/media/native/midi/MidiDeviceInfo.h
+++ b/media/native/midi/MidiDeviceInfo.h
@@ -38,6 +38,7 @@ public:
int getType() const { return mType; }
int getUid() const { return mId; }
bool isPrivate() const { return mIsPrivate; }
+ int getDefaultProtocol() const { return mDefaultProtocol; }
const Vector<String16>& getInputPortNames() const { return mInputPortNames; }
const Vector<String16>& getOutputPortNames() const { return mOutputPortNames; }
String16 getProperty(const char* propertyName);
@@ -48,6 +49,18 @@ public:
TYPE_VIRTUAL = 2,
TYPE_BLUETOOTH = 3,
};
+
+ enum {
+ PROTOCOL_UMP_USE_MIDI_CI = 0,
+ PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1,
+ PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2,
+ PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3,
+ PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4,
+ PROTOCOL_UMP_MIDI_2_0 = 17,
+ PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18,
+ PROTOCOL_UNKNOWN = -1,
+ };
+
static const char* const PROPERTY_NAME;
static const char* const PROPERTY_MANUFACTURER;
static const char* const PROPERTY_PRODUCT;
@@ -72,6 +85,7 @@ private:
Vector<String16> mOutputPortNames;
os::PersistableBundle mProperties;
bool mIsPrivate;
+ int32_t mDefaultProtocol;
};
} // namespace midi
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index f90796e415c0..aa076e85e30d 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -138,6 +138,7 @@ static media_status_t AMIDI_getDeviceInfo(const AMidiDevice *device,
outDeviceInfoPtr->type = deviceInfo.getType();
outDeviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
outDeviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
+ outDeviceInfoPtr->defaultProtocol = deviceInfo.getDefaultProtocol();
return AMEDIA_OK;
}
@@ -238,6 +239,13 @@ ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) {
return device->deviceInfo.outputPortCount;
}
+AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device) {
+ if (device == nullptr) {
+ return AMIDI_DEVICE_PROTOCOL_UNKNOWN;
+ }
+ return static_cast<AMidiDevice_Protocol>(device->deviceInfo.defaultProtocol);
+}
+
/*
* Port Helpers
*/
diff --git a/media/native/midi/amidi_internal.h b/media/native/midi/amidi_internal.h
index fce85963d217..023a6f5ec900 100644
--- a/media/native/midi/amidi_internal.h
+++ b/media/native/midi/amidi_internal.h
@@ -25,6 +25,7 @@ typedef struct {
int32_t type; /* one of AMIDI_DEVICE_TYPE_* constants */
int32_t inputPortCount; /* number of input (send) ports associated with the device */
int32_t outputPortCount; /* number of output (received) ports associated with the device */
+ int32_t defaultProtocol; /* one of the AMIDI_DEVICE_PROTOCOL_* constants */
} AMidiDeviceInfo;
struct AMidiDevice {
diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h
index 742db34b74a7..fbb7fb329659 100644
--- a/media/native/midi/include/amidi/AMidi.h
+++ b/media/native/midi/include/amidi/AMidi.h
@@ -62,6 +62,78 @@ enum {
};
/*
+ * Protocol IDs for various MIDI devices.
+ *
+ * Introduced in API 33.
+ */
+enum AMidiDevice_Protocol : int32_t {
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use UMP to negotiate with the device with MIDI-CI.
+ * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
+ */
+ AMIDI_DEVICE_PROTOCOL_UMP_USE_MIDI_CI = 0,
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 1.0 through UMP with packet sizes up to 64 bits.
+ */
+ AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1,
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 1.0 through UMP with packet sizes up to 64 bits and jitter reduction timestamps.
+ */
+ AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2,
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 1.0 through UMP with packet sizes up to 128 bits.
+ */
+ AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3,
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 1.0 through UMP with packet sizes up to 128 bits and jitter reduction timestamps.
+ */
+ AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4,
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 2.0 through UMP.
+ */
+ AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0 = 17,
+
+ /**
+ * Constant representing a default protocol with Universal MIDI Packets (UMP).
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * All UMP data should be a multiple of 4 bytes.
+ * Use MIDI 2.0 through UMP and jitter reduction timestamps.
+ */
+ AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18,
+
+ /**
+ * Constant representing a device with an unknown default protocol.
+ * If Universal MIDI Packets (UMP) are needed, use MIDI-CI through MIDI 1.0.
+ * UMP is defined in "Universal MIDI Packet (UMP) Format and MIDI 2.0 Protocol" spec.
+ * MIDI-CI is defined in "MIDI Capability Inquiry (MIDI-CI)" spec.
+ */
+ AMIDI_DEVICE_PROTOCOL_UNKNOWN = -1
+};
+
+/*
* Device API
*/
/**
@@ -134,6 +206,30 @@ ssize_t AMIDI_API AMidiDevice_getNumInputPorts(const AMidiDevice *device) __INTR
*/
ssize_t AMIDI_API AMidiDevice_getNumOutputPorts(const AMidiDevice *device) __INTRODUCED_IN(29);
+/**
+ * Gets the MIDI device default protocol.
+ *
+ * @param device Specifies the MIDI device.
+ *
+ * @return The identifier of the MIDI device default protocol:
+ * AMIDI_DEVICE_PROTOCOL_UMP_USE_MIDI_CI
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0
+ * AMIDI_DEVICE_PROTOCOL_UMP_MIDI_2_0_AND_JRTS
+ * AMIDI_DEVICE_PROTOCOL_UNKNOWN
+ *
+ * Most devices should return PROTOCOL_UNKNOWN (-1). This is intentional as devices
+ * with default UMP support are not backwards compatible. When the device is null,
+ * return AMIDI_DEVICE_PROTOCOL_UNKNOWN.
+ *
+ * Available since API 33.
+ */
+AMidiDevice_Protocol AMIDI_API AMidiDevice_getDefaultProtocol(const AMidiDevice *device)
+ __INTRODUCED_IN(33);
+
/*
* API for receiving data from the Output port of a device.
*/
diff --git a/media/native/midi/libamidi.map.txt b/media/native/midi/libamidi.map.txt
index 62627f8c5ef7..f25f97770b50 100644
--- a/media/native/midi/libamidi.map.txt
+++ b/media/native/midi/libamidi.map.txt
@@ -2,6 +2,7 @@ LIBAMIDI {
global:
AMidiDevice_fromJava;
AMidiDevice_release;
+ AMidiDevice_getDefaultProtocol; # introduced=Tiramisu
AMidiDevice_getType;
AMidiDevice_getNumInputPorts;
AMidiDevice_getNumOutputPorts;
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 62c313ace306..2dd9525867b6 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -260,7 +260,8 @@ public final class BluetoothMidiDevice {
inputPortReceivers[0] = mEventScheduler.getReceiver();
mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1,
- null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, mDeviceServerCallback);
+ null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH,
+ MidiDeviceInfo.PROTOCOL_UNKNOWN, mDeviceServerCallback);
mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0];
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index d0205ae24f85..e4e0ff0a81db 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -638,13 +638,31 @@ public class MidiService extends IMidiManager.Stub {
private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0];
public MidiDeviceInfo[] getDevices() {
+ return getDevicesForTransport(MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+ }
+
+ /**
+ * @hide
+ */
+ public MidiDeviceInfo[] getDevicesForTransport(int transport) {
ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
int uid = Binder.getCallingUid();
synchronized (mDevicesByInfo) {
for (Device device : mDevicesByInfo.values()) {
if (device.isUidAllowed(uid)) {
- deviceInfos.add(device.getDeviceInfo());
+ // UMP devices have protocols that are not PROTOCOL_UNKNOWN
+ if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
+ if (device.getDeviceInfo().getDefaultProtocol()
+ != MidiDeviceInfo.PROTOCOL_UNKNOWN) {
+ deviceInfos.add(device.getDeviceInfo());
+ }
+ } else if (transport == MidiManager.TRANSPORT_MIDI_BYTE_STREAM) {
+ if (device.getDeviceInfo().getDefaultProtocol()
+ == MidiDeviceInfo.PROTOCOL_UNKNOWN) {
+ deviceInfos.add(device.getDeviceInfo());
+ }
+ }
}
}
}
@@ -713,7 +731,7 @@ public class MidiService extends IMidiManager.Stub {
@Override
public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
- Bundle properties, int type) {
+ Bundle properties, int type, int defaultProtocol) {
int uid = Binder.getCallingUid();
if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
throw new SecurityException("only system can create USB devices");
@@ -723,7 +741,8 @@ public class MidiService extends IMidiManager.Stub {
synchronized (mDevicesByInfo) {
return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
- outputPortNames, properties, server, null, false, uid);
+ outputPortNames, properties, server, null, false, uid,
+ defaultProtocol);
}
}
@@ -792,11 +811,12 @@ public class MidiService extends IMidiManager.Stub {
private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
String[] inputPortNames, String[] outputPortNames, Bundle properties,
IMidiDeviceServer server, ServiceInfo serviceInfo,
- boolean isPrivate, int uid) {
+ boolean isPrivate, int uid, int defaultProtocol) {
int id = mNextDeviceId++;
MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
- inputPortNames, outputPortNames, properties, isPrivate);
+ inputPortNames, outputPortNames, properties, isPrivate,
+ defaultProtocol);
if (server != null) {
try {
@@ -983,10 +1003,11 @@ public class MidiService extends IMidiManager.Stub {
synchronized (mDevicesByInfo) {
addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL,
- numInputPorts, numOutputPorts,
- inputPortNames.toArray(EMPTY_STRING_ARRAY),
- outputPortNames.toArray(EMPTY_STRING_ARRAY),
- properties, null, serviceInfo, isPrivate, uid);
+ numInputPorts, numOutputPorts,
+ inputPortNames.toArray(EMPTY_STRING_ARRAY),
+ outputPortNames.toArray(EMPTY_STRING_ARRAY),
+ properties, null, serviceInfo, isPrivate, uid,
+ MidiDeviceInfo.PROTOCOL_UNKNOWN);
}
// setting properties to null signals that we are no longer
// processing a <device>
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 0aa62c53e269..1c72eb8db708 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -257,10 +257,11 @@ public final class UsbAlsaManager {
// look for MIDI devices
boolean hasMidi = parser.hasMIDIInterface();
- int midiNumInputs = parser.calculateNumMidiInputs();
- int midiNumOutputs = parser.calculateNumMidiOutputs();
+ int midiNumInputs = parser.calculateNumLegacyMidiInputs();
+ int midiNumOutputs = parser.calculateNumLegacyMidiOutputs();
if (DEBUG) {
Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
+ Slog.d(TAG, "midiNumInputs: " + midiNumInputs + " midiNumOutputs:" + midiNumOutputs);
}
if (hasMidi && mHasMidiFeature) {
int device = 0;
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index f33001c9241e..9ac270f17fc4 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.os.Bundle;
@@ -46,6 +47,8 @@ import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
import com.android.server.usb.descriptors.report.TextReportCanvas;
import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
+import libcore.io.IoUtils;
+
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
@@ -90,6 +93,13 @@ public class UsbHostManager {
private ConnectionRecord mLastConnect;
private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>();
+ /**
+ * List of connected MIDI devices
+ */
+ private final HashMap<String, UsbUniversalMidiDevice>
+ mMidiDevices = new HashMap<String, UsbUniversalMidiDevice>();
+ private final boolean mHasMidiFeature;
+
/*
* ConnectionRecord
* Stores connection/disconnection data.
@@ -245,6 +255,7 @@ public class UsbHostManager {
setUsbDeviceConnectionHandler(ComponentName.unflattenFromString(
deviceConnectionHandler));
}
+ mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
}
public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
@@ -413,6 +424,18 @@ public class UsbHostManager {
mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
+ if (mHasMidiFeature) {
+ if (parser.containsUniversalMidiDeviceEndpoint()) {
+ UsbUniversalMidiDevice midiDevice = UsbUniversalMidiDevice.create(mContext,
+ newDevice, parser);
+ if (midiDevice != null) {
+ mMidiDevices.put(deviceAddress, midiDevice);
+ } else {
+ Slog.e(TAG, "Universal Midi Device is null.");
+ }
+ }
+ }
+
// Tracking
addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
parser.getRawDescriptors());
@@ -446,6 +469,14 @@ public class UsbHostManager {
Slog.d(TAG, "Removed device at " + deviceAddress + ": " + device.getProductName());
mUsbAlsaManager.usbDeviceRemoved(deviceAddress);
mPermissionManager.usbDeviceRemoved(device);
+
+ // MIDI
+ UsbUniversalMidiDevice midiDevice = mMidiDevices.remove(deviceAddress);
+ if (midiDevice != null) {
+ Slog.i(TAG, "USB Universal MIDI Device Removed: " + deviceAddress);
+ IoUtils.closeQuietly(midiDevice);
+ }
+
getCurrentUserSettings().usbDeviceRemoved(device);
ConnectionRecord current = mConnected.get(deviceAddress);
// Tracking
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index b61e93b36e58..275f21755141 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -303,7 +303,8 @@ public final class UsbMidiDevice implements Closeable {
}
mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs,
- null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback);
+ null, null, properties, MidiDeviceInfo.TYPE_USB,
+ MidiDeviceInfo.PROTOCOL_UNKNOWN, mCallback);
if (mServer == null) {
return false;
}
diff --git a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
new file mode 100644
index 000000000000..db0c80f189d3
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbManager;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceServer;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiReceiver;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
+import com.android.server.usb.descriptors.UsbEndpointDescriptor;
+import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
+import com.android.server.usb.descriptors.UsbMidiBlockParser;
+
+import libcore.io.IoUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * A MIDI device that opens device connections to MIDI 2.0 endpoints.
+ */
+public final class UsbUniversalMidiDevice implements Closeable {
+ private static final String TAG = "UsbUniversalMidiDevice";
+ private static final boolean DEBUG = false;
+
+ private Context mContext;
+ private UsbDevice mUsbDevice;
+ private UsbDescriptorParser mParser;
+ private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces;
+
+ // USB outputs are MIDI inputs
+ private final InputReceiverProxy[] mMidiInputPortReceivers;
+ private final int mNumInputs;
+ private final int mNumOutputs;
+
+ private MidiDeviceServer mServer;
+
+ // event schedulers for each input port of the physical device
+ private MidiEventScheduler[] mEventSchedulers;
+
+ private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
+ private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
+ private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
+
+ private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser();
+ private int mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
+
+ private final Object mLock = new Object();
+ private boolean mIsOpen;
+
+ private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
+
+ @Override
+ public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) {
+ MidiDeviceInfo deviceInfo = status.getDeviceInfo();
+ int numInputPorts = deviceInfo.getInputPortCount();
+ int numOutputPorts = deviceInfo.getOutputPortCount();
+ boolean hasOpenPorts = false;
+
+ for (int i = 0; i < numInputPorts; i++) {
+ if (status.isInputPortOpen(i)) {
+ hasOpenPorts = true;
+ break;
+ }
+ }
+
+ if (!hasOpenPorts) {
+ for (int i = 0; i < numOutputPorts; i++) {
+ if (status.getOutputPortOpenCount(i) > 0) {
+ hasOpenPorts = true;
+ break;
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ if (hasOpenPorts && !mIsOpen) {
+ openLocked();
+ } else if (!hasOpenPorts && mIsOpen) {
+ closeLocked();
+ }
+ }
+ }
+
+ @Override
+ public void onClose() {
+ }
+ };
+
+ // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist
+ // until the device has active clients
+ private static final class InputReceiverProxy extends MidiReceiver {
+ private MidiReceiver mReceiver;
+
+ @Override
+ public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ MidiReceiver receiver = mReceiver;
+ if (receiver != null) {
+ receiver.send(msg, offset, count, timestamp);
+ }
+ }
+
+ public void setReceiver(MidiReceiver receiver) {
+ mReceiver = receiver;
+ }
+
+ @Override
+ public void onFlush() throws IOException {
+ MidiReceiver receiver = mReceiver;
+ if (receiver != null) {
+ receiver.flush();
+ }
+ }
+ }
+
+ /**
+ * Creates an UsbUniversalMidiDevice based on the input parameters. Read/Write streams
+ * will be created individually as some devices don't have the same number of
+ * inputs and outputs.
+ */
+ public static UsbUniversalMidiDevice create(Context context, UsbDevice usbDevice,
+ UsbDescriptorParser parser) {
+ UsbUniversalMidiDevice midiDevice = new UsbUniversalMidiDevice(usbDevice, parser);
+ if (!midiDevice.register(context)) {
+ IoUtils.closeQuietly(midiDevice);
+ Log.e(TAG, "createDeviceServer failed");
+ return null;
+ }
+ return midiDevice;
+ }
+
+ private UsbUniversalMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser) {
+ mUsbDevice = usbDevice;
+ mParser = parser;
+
+ mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
+
+ int numInputs = 0;
+ int numOutputs = 0;
+
+ for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
+ UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
+ for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
+ endpointIndex++) {
+ UsbEndpointDescriptor endpoint =
+ interfaceDescriptor.getEndpointDescriptor(endpointIndex);
+ // 0 is output, 1 << 7 is input.
+ if (endpoint.getDirection() == 0) {
+ numOutputs++;
+ } else {
+ numInputs++;
+ }
+ }
+ }
+
+ mNumInputs = numInputs;
+ mNumOutputs = numOutputs;
+
+ Log.d(TAG, "Created UsbUniversalMidiDevice with " + numInputs + " inputs and "
+ + numOutputs + " outputs");
+
+ // Create MIDI port receivers based on the number of output ports. The
+ // output of USB is the input of MIDI.
+ mMidiInputPortReceivers = new InputReceiverProxy[numOutputs];
+ for (int port = 0; port < numOutputs; port++) {
+ mMidiInputPortReceivers[port] = new InputReceiverProxy();
+ }
+ }
+
+ private int calculateDefaultMidiProtocol() {
+ UsbManager manager = mContext.getSystemService(UsbManager.class);
+
+ for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
+ UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
+ boolean doesInterfaceContainInput = false;
+ boolean doesInterfaceContainOutput = false;
+ for (int endpointIndex = 0; (endpointIndex < interfaceDescriptor.getNumEndpoints())
+ && !(doesInterfaceContainInput && doesInterfaceContainOutput);
+ endpointIndex++) {
+ UsbEndpointDescriptor endpoint =
+ interfaceDescriptor.getEndpointDescriptor(endpointIndex);
+ // 0 is output, 1 << 7 is input.
+ if (endpoint.getDirection() == 0) {
+ doesInterfaceContainOutput = true;
+ } else {
+ doesInterfaceContainInput = true;
+ }
+ }
+
+ // Intentionally open the device connection to query the default MIDI type for
+ // a connection with both the input and output set.
+ if (doesInterfaceContainInput
+ && doesInterfaceContainOutput) {
+ UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
+ if (!connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true)) {
+ Log.d(TAG, "Can't claim control interface");
+ continue;
+ }
+ int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
+ interfaceDescriptor.getInterfaceNumber(),
+ interfaceDescriptor.getAlternateSetting());
+
+ connection.close();
+ return defaultMidiProtocol;
+ }
+ }
+
+ Log.d(TAG, "Cannot find interface with both input and output endpoints");
+ return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
+ }
+
+ private boolean openLocked() {
+ UsbManager manager = mContext.getSystemService(UsbManager.class);
+
+ mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size());
+ mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size());
+ mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size());
+
+ for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
+ ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>();
+ ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>();
+ UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
+ for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
+ endpointIndex++) {
+ UsbEndpointDescriptor endpoint =
+ interfaceDescriptor.getEndpointDescriptor(endpointIndex);
+ // 0 is output, 1 << 7 is input.
+ if (endpoint.getDirection() == 0) {
+ outputEndpoints.add(endpoint.toAndroid(mParser));
+ } else {
+ inputEndpoints.add(endpoint.toAndroid(mParser));
+ }
+ }
+ if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
+ UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
+ connection.setInterface(interfaceDescriptor.toAndroid(mParser));
+ connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true);
+ mUsbDeviceConnections.add(connection);
+ mInputUsbEndpoints.add(inputEndpoints);
+ mOutputUsbEndpoints.add(outputEndpoints);
+ }
+ }
+
+ mEventSchedulers = new MidiEventScheduler[mNumOutputs];
+
+ for (int i = 0; i < mNumOutputs; i++) {
+ MidiEventScheduler scheduler = new MidiEventScheduler();
+ mEventSchedulers[i] = scheduler;
+ mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver());
+ }
+
+ final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
+
+ // Create input thread for each input port of the physical device
+ int portNumber = 0;
+ for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size();
+ connectionIndex++) {
+ for (int endpointIndex = 0;
+ endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
+ endpointIndex++) {
+ final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
+ final UsbEndpoint epF = mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
+ final int portF = portNumber;
+
+ new Thread("UsbUniversalMidiDevice input thread " + portF) {
+ @Override
+ public void run() {
+ byte[] inputBuffer = new byte[epF.getMaxPacketSize()];
+ try {
+ while (true) {
+ // Record time of event immediately after waking.
+ long timestamp = System.nanoTime();
+ synchronized (mLock) {
+ if (!mIsOpen) break;
+
+ int nRead = connectionF.bulkTransfer(epF, inputBuffer,
+ inputBuffer.length, 0);
+
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ swapEndiannessPerWord(inputBuffer, inputBuffer.length);
+
+ if (nRead > 0) {
+ if (DEBUG) {
+ logByteArray("Input ", inputBuffer, 0,
+ nRead);
+ }
+ outputReceivers[portF].send(inputBuffer, 0, nRead,
+ timestamp);
+ }
+ }
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "reader thread exiting");
+ }
+ Log.d(TAG, "input thread exit");
+ }
+ }.start();
+
+ portNumber++;
+ }
+ }
+
+ // Create output thread for each output port of the physical device
+ portNumber = 0;
+ for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size();
+ connectionIndex++) {
+ for (int endpointIndex = 0;
+ endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
+ endpointIndex++) {
+ final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
+ final UsbEndpoint epF =
+ mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
+ final int portF = portNumber;
+ final MidiEventScheduler eventSchedulerF = mEventSchedulers[portF];
+
+ new Thread("UsbUniversalMidiDevice output thread " + portF) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent) eventSchedulerF.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ swapEndiannessPerWord(event.data, event.count);
+
+ if (DEBUG) {
+ logByteArray("Output ", event.data, 0,
+ event.count);
+ }
+ connectionF.bulkTransfer(epF, event.data, event.count, 0);
+ eventSchedulerF.addEventToPool(event);
+ }
+ Log.d(TAG, "output thread exit");
+ }
+ }.start();
+
+ portNumber++;
+ }
+ }
+
+ mIsOpen = true;
+ return true;
+ }
+
+ private boolean register(Context context) {
+ mContext = context;
+ MidiManager midiManager = context.getSystemService(MidiManager.class);
+ if (midiManager == null) {
+ Log.e(TAG, "No MidiManager in UsbUniversalMidiDevice.create()");
+ return false;
+ }
+
+ mDefaultMidiProtocol = calculateDefaultMidiProtocol();
+
+ Bundle properties = new Bundle();
+ String manufacturer = mUsbDevice.getManufacturerName();
+ String product = mUsbDevice.getProductName();
+ String version = mUsbDevice.getVersion();
+ String name;
+ if (manufacturer == null || manufacturer.isEmpty()) {
+ name = product;
+ } else if (product == null || product.isEmpty()) {
+ name = manufacturer;
+ } else {
+ name = manufacturer + " " + product + " MIDI 2.0";
+ }
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
+ properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
+ properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
+ properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
+ properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
+ mUsbDevice.getSerialNumber());
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, mUsbDevice);
+
+ mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs,
+ null, null, properties, MidiDeviceInfo.TYPE_USB, mDefaultMidiProtocol, mCallback);
+ if (mServer == null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void close() throws IOException {
+ synchronized (mLock) {
+ if (mIsOpen) {
+ closeLocked();
+ }
+ }
+
+ if (mServer != null) {
+ IoUtils.closeQuietly(mServer);
+ }
+ }
+
+ private void closeLocked() {
+ for (int i = 0; i < mEventSchedulers.length; i++) {
+ mMidiInputPortReceivers[i].setReceiver(null);
+ mEventSchedulers[i].close();
+ }
+ for (UsbDeviceConnection connection : mUsbDeviceConnections) {
+ connection.close();
+ }
+ mUsbDeviceConnections = null;
+ mInputUsbEndpoints = null;
+ mOutputUsbEndpoints = null;
+
+ mIsOpen = false;
+ }
+
+ private void swapEndiannessPerWord(byte[] array, int size) {
+ for (int i = 0; i + 3 < size; i += 4) {
+ byte tmp = array[i];
+ array[i] = array[i + 3];
+ array[i + 3] = tmp;
+ tmp = array[i + 1];
+ array[i + 1] = array[i + 2];
+ array[i + 2] = tmp;
+ }
+ }
+
+ private static void logByteArray(String prefix, byte[] value, int offset, int count) {
+ StringBuilder builder = new StringBuilder(prefix);
+ for (int i = offset; i < offset + count; i++) {
+ builder.append(String.format("0x%02X", value[i]));
+ if (i != value.length - 1) {
+ builder.append(", ");
+ }
+ }
+ Log.d(TAG, builder.toString());
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
index 409e605c3c2f..bfcf62147f75 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
@@ -38,8 +38,8 @@ public class UsbACAudioControlEndpoint extends UsbACEndpoint {
static final byte ATTRIBSMASK_SYNC = 0x0C;
static final byte ATTRIBMASK_TRANS = 0x03;
- public UsbACAudioControlEndpoint(int length, byte type, int subclass) {
- super(length, type, subclass);
+ public UsbACAudioControlEndpoint(int length, byte type, int subclass, byte subtype) {
+ super(length, type, subclass, subtype);
}
public byte getAddress() {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
index e63bb74abdf7..ae9ca0d827f5 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
@@ -24,8 +24,8 @@ public class UsbACAudioStreamEndpoint extends UsbACEndpoint {
private static final String TAG = "UsbACAudioStreamEndpoint";
//TODO data fields...
- public UsbACAudioStreamEndpoint(int length, byte type, int subclass) {
- super(length, type, subclass);
+ public UsbACAudioStreamEndpoint(int length, byte type, int subclass, byte subtype) {
+ super(length, type, subclass, subtype);
}
@Override
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
index ff7f3934086c..b7f9ac334954 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -25,13 +25,17 @@ import android.util.Log;
abstract class UsbACEndpoint extends UsbDescriptor {
private static final String TAG = "UsbACEndpoint";
+ public static final byte MS_GENERAL = 1;
+ public static final byte MS_GENERAL_2_0 = 2;
+
protected final int mSubclass; // from the mSubclass member of the "enclosing"
// Interface Descriptor, not the stream.
- protected byte mSubtype; // 2:1 HEADER descriptor subtype
+ protected final byte mSubtype; // 2:1 HEADER descriptor subtype
- UsbACEndpoint(int length, byte type, int subclass) {
+ UsbACEndpoint(int length, byte type, int subclass, byte subtype) {
super(length, type);
mSubclass = subclass;
+ mSubtype = subtype;
}
public int getSubclass() {
@@ -44,33 +48,39 @@ abstract class UsbACEndpoint extends UsbDescriptor {
@Override
public int parseRawDescriptors(ByteStream stream) {
- mSubtype = stream.getByte();
return mLength;
}
public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
- int length, byte type) {
+ int length, byte type, byte subType) {
UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
int subClass = interfaceDesc.getUsbSubclass();
- // TODO shouldn't this switch on subtype?
switch (subClass) {
case AUDIO_AUDIOCONTROL:
if (UsbDescriptorParser.DEBUG) {
Log.d(TAG, "---> AUDIO_AUDIOCONTROL");
}
- return new UsbACAudioControlEndpoint(length, type, subClass);
+ return new UsbACAudioControlEndpoint(length, type, subClass, subType);
case AUDIO_AUDIOSTREAMING:
if (UsbDescriptorParser.DEBUG) {
Log.d(TAG, "---> AUDIO_AUDIOSTREAMING");
}
- return new UsbACAudioStreamEndpoint(length, type, subClass);
+ return new UsbACAudioStreamEndpoint(length, type, subClass, subType);
case AUDIO_MIDISTREAMING:
if (UsbDescriptorParser.DEBUG) {
Log.d(TAG, "---> AUDIO_MIDISTREAMING");
}
- return new UsbACMidiEndpoint(length, type, subClass);
+ switch (subType) {
+ case MS_GENERAL:
+ return new UsbACMidi10Endpoint(length, type, subClass, subType);
+ case MS_GENERAL_2_0:
+ return new UsbACMidi20Endpoint(length, type, subClass, subType);
+ default:
+ Log.w(TAG, "Unknown Midi Endpoint id:0x" + Integer.toHexString(subType));
+ return null;
+ }
default:
Log.w(TAG, "Unknown Audio Class Endpoint id:0x" + Integer.toHexString(subClass));
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java
index 42ee88922edd..49b9d7b30d29 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi10Endpoint.java
@@ -22,14 +22,14 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
* An audio class-specific Midi Endpoint.
* see midi10.pdf section 6.2.2
*/
-public final class UsbACMidiEndpoint extends UsbACEndpoint {
- private static final String TAG = "UsbACMidiEndpoint";
+public final class UsbACMidi10Endpoint extends UsbACEndpoint {
+ private static final String TAG = "UsbACMidi10Endpoint";
private byte mNumJacks;
- private byte[] mJackIds;
+ private byte[] mJackIds = new byte[0];
- public UsbACMidiEndpoint(int length, byte type, int subclass) {
- super(length, type, subclass);
+ public UsbACMidi10Endpoint(int length, byte type, int subclass, byte subtype) {
+ super(length, type, subclass, subtype);
}
public byte getNumJacks() {
@@ -45,9 +45,11 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint {
super.parseRawDescriptors(stream);
mNumJacks = stream.getByte();
- mJackIds = new byte[mNumJacks];
- for (int jack = 0; jack < mNumJacks; jack++) {
- mJackIds[jack] = stream.getByte();
+ if (mNumJacks > 0) {
+ mJackIds = new byte[mNumJacks];
+ for (int jack = 0; jack < mNumJacks; jack++) {
+ mJackIds[jack] = stream.getByte();
+ }
}
return mLength;
}
@@ -56,10 +58,10 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint {
public void report(ReportCanvas canvas) {
super.report(canvas);
- canvas.writeHeader(3, "AC Midi Endpoint: " + ReportCanvas.getHexString(getType())
+ canvas.writeHeader(3, "ACMidi10Endpoint: " + ReportCanvas.getHexString(getType())
+ " Length: " + getLength());
canvas.openList();
canvas.writeListItem("" + getNumJacks() + " Jacks.");
canvas.closeList();
}
-} \ No newline at end of file
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java
new file mode 100644
index 000000000000..1024a5bf55a6
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidi20Endpoint.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.server.usb.descriptors.report.ReportCanvas;
+
+/**
+ * @hide
+ * An audio class-specific Midi Endpoint.
+ * see midi10.pdf section 6.2.2
+ */
+public final class UsbACMidi20Endpoint extends UsbACEndpoint {
+ private static final String TAG = "UsbACMidi20Endpoint";
+
+ private byte mNumGroupTerminals;
+ private byte[] mBlockIds = new byte[0];
+
+ public UsbACMidi20Endpoint(int length, byte type, int subclass, byte subtype) {
+ super(length, type, subclass, subtype);
+ }
+
+ public byte getNumGroupTerminals() {
+ return mNumGroupTerminals;
+ }
+
+ public byte[] getBlockIds() {
+ return mBlockIds;
+ }
+
+ @Override
+ public int parseRawDescriptors(ByteStream stream) {
+ super.parseRawDescriptors(stream);
+
+ mNumGroupTerminals = stream.getByte();
+ if (mNumGroupTerminals > 0) {
+ mBlockIds = new byte[mNumGroupTerminals];
+ for (int block = 0; block < mNumGroupTerminals; block++) {
+ mBlockIds[block] = stream.getByte();
+ }
+ }
+ return mLength;
+ }
+
+ @Override
+ public void report(ReportCanvas canvas) {
+ super.report(canvas);
+
+ canvas.writeHeader(3, "AC Midi20 Endpoint: " + ReportCanvas.getHexString(getType())
+ + " Length: " + getLength());
+ canvas.openList();
+ canvas.writeListItem("" + getNumGroupTerminals() + " Group Terminals.");
+ canvas.closeList();
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 7250a071835d..3412a6f80cc7 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -30,6 +30,9 @@ public final class UsbDescriptorParser {
private final String mDeviceAddr;
+ private static final int MS_MIDI_1_0 = 0x0100;
+ private static final int MS_MIDI_2_0 = 0x0200;
+
// Descriptor Objects
private static final int DESCRIPTORS_ALLOC_SIZE = 128;
private final ArrayList<UsbDescriptor> mDescriptors;
@@ -215,6 +218,7 @@ public final class UsbDescriptorParser {
Log.w(TAG, " Unparsed Class-specific");
break;
}
+ mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor);
}
break;
@@ -222,17 +226,25 @@ public final class UsbDescriptorParser {
if (mCurInterfaceDescriptor != null) {
int subClass = mCurInterfaceDescriptor.getUsbClass();
switch (subClass) {
- case UsbDescriptor.CLASSID_AUDIO:
- descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
+ case UsbDescriptor.CLASSID_AUDIO: {
+ Byte subType = stream.getByte();
+ if (DEBUG) {
+ Log.d(TAG, "UsbDescriptor.CLASSID_AUDIO type:0x"
+ + Integer.toHexString(type));
+ }
+ descriptor = UsbACEndpoint.allocDescriptor(this, length, type,
+ subType);
+ }
break;
case UsbDescriptor.CLASSID_VIDEO: {
- Byte subtype = stream.getByte();
+ Byte subType = stream.getByte();
if (DEBUG) {
Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO type:0x"
+ Integer.toHexString(type));
}
- descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, subtype);
+ descriptor = UsbVCEndpoint.allocDescriptor(this, length, type,
+ subType);
}
break;
@@ -644,8 +656,8 @@ public final class UsbDescriptorParser {
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) {
+ UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
+ if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
return true;
}
} else {
@@ -656,17 +668,90 @@ public final class UsbDescriptorParser {
return false;
}
- private int calculateNumMidiPorts(boolean isOutput) {
+ /**
+ * @hide
+ */
+ public boolean containsUniversalMidiDeviceEndpoint() {
+ ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
+ findUniversalMidiInterfaceDescriptors();
+ int outputCount = 0;
+ int inputCount = 0;
+ for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size();
+ interfaceIndex++) {
+ UsbInterfaceDescriptor interfaceDescriptor = interfaceDescriptors.get(interfaceIndex);
+ for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
+ endpointIndex++) {
+ UsbEndpointDescriptor endpoint =
+ interfaceDescriptor.getEndpointDescriptor(endpointIndex);
+ // 0 is output, 1 << 7 is input.
+ if (endpoint.getDirection() == 0) {
+ outputCount++;
+ } else {
+ inputCount++;
+ }
+ }
+ }
+ return (outputCount > 0) || (inputCount > 0);
+ }
+
+ /**
+ * @hide
+ */
+ public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() {
int count = 0;
ArrayList<UsbDescriptor> descriptors =
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
+ ArrayList<UsbInterfaceDescriptor> universalMidiInterfaces =
+ new ArrayList<UsbInterfaceDescriptor>();
+
for (UsbDescriptor descriptor : descriptors) {
// ensure that this isn't an unrecognized interface descriptor
if (descriptor instanceof UsbInterfaceDescriptor) {
- UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor;
- if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
- for (int i = 0; i < interfaceDescr.getNumEndpoints(); i++) {
- UsbEndpointDescriptor endpoint = interfaceDescr.getEndpointDescriptor(i);
+ UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
+ if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
+ UsbDescriptor classSpecificDescriptor =
+ interfaceDescriptor.getClassSpecificInterfaceDescriptor();
+ if (classSpecificDescriptor != null) {
+ if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
+ UsbMSMidiHeader midiHeader =
+ (UsbMSMidiHeader) classSpecificDescriptor;
+ if (midiHeader.getMidiStreamingClass() == MS_MIDI_2_0) {
+ universalMidiInterfaces.add(interfaceDescriptor);
+ }
+ }
+ }
+ }
+ } else {
+ Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength()
+ + " t:0x" + Integer.toHexString(descriptor.getType()));
+ }
+ }
+ return universalMidiInterfaces;
+ }
+
+ private int calculateNumLegacyMidiPorts(boolean isOutput) {
+ int count = 0;
+ ArrayList<UsbDescriptor> descriptors =
+ getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
+ for (UsbDescriptor descriptor : descriptors) {
+ // ensure that this isn't an unrecognized interface descriptor
+ if (descriptor instanceof UsbInterfaceDescriptor) {
+ UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
+ if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
+ UsbDescriptor classSpecificDescriptor =
+ interfaceDescriptor.getClassSpecificInterfaceDescriptor();
+ if (classSpecificDescriptor != null) {
+ if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
+ UsbMSMidiHeader midiHeader =
+ (UsbMSMidiHeader) classSpecificDescriptor;
+ if (midiHeader.getMidiStreamingClass() != MS_MIDI_1_0) {
+ continue;
+ }
+ }
+ }
+ for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
+ UsbEndpointDescriptor endpoint =
+ interfaceDescriptor.getEndpointDescriptor(i);
// 0 is output, 1 << 7 is input.
if ((endpoint.getDirection() == 0) == isOutput) {
count++;
@@ -684,15 +769,15 @@ public final class UsbDescriptorParser {
/**
* @hide
*/
- public int calculateNumMidiInputs() {
- return calculateNumMidiPorts(false /*isOutput*/);
+ public int calculateNumLegacyMidiInputs() {
+ return calculateNumLegacyMidiPorts(false /*isOutput*/);
}
/**
* @hide
*/
- public int calculateNumMidiOutputs() {
- return calculateNumMidiPorts(true /*isOutput*/);
+ public int calculateNumLegacyMidiOutputs() {
+ return calculateNumLegacyMidiPorts(true /*isOutput*/);
}
/**
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 4d0cfea98630..ab07ce7fdb7a 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -112,7 +112,10 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION;
}
- /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
+ /**
+ * Returns a UsbEndpoint that this UsbEndpointDescriptor is describing.
+ */
+ public UsbEndpoint toAndroid(UsbDescriptorParser parser) {
if (UsbDescriptorParser.DEBUG) {
Log.d(TAG, "toAndroid() type:"
+ Integer.toHexString(mAttributes & MASK_ATTRIBS_TRANSTYPE)
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 64dbd971cc67..ca4613b17873 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -42,6 +42,8 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
new ArrayList<UsbEndpointDescriptor>();
+ private UsbDescriptor mClassSpecificInterfaceDescriptor;
+
UsbInterfaceDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 3;
@@ -105,7 +107,18 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
mEndpointDescriptors.add(endpoint);
}
- UsbInterface toAndroid(UsbDescriptorParser parser) {
+ public void setClassSpecificInterfaceDescriptor(UsbDescriptor descriptor) {
+ mClassSpecificInterfaceDescriptor = descriptor;
+ }
+
+ public UsbDescriptor getClassSpecificInterfaceDescriptor() {
+ return mClassSpecificInterfaceDescriptor;
+ }
+
+ /**
+ * Returns a UsbInterface that this UsbInterfaceDescriptor is describing.
+ */
+ public UsbInterface toAndroid(UsbDescriptorParser parser) {
if (UsbDescriptorParser.DEBUG) {
Log.d(TAG, "toAndroid() class:" + Integer.toHexString(mUsbClass)
+ " subclass:" + Integer.toHexString(mUsbSubclass)
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
index d0ca6db87d38..76535612f54d 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
@@ -25,13 +25,19 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
public final class UsbMSMidiHeader extends UsbACInterface {
private static final String TAG = "UsbMSMidiHeader";
+ private int mMidiStreamingClass; // MSC Specification Release (BCD).
+
public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
+ public int getMidiStreamingClass() {
+ return mMidiStreamingClass;
+ }
+
@Override
public int parseRawDescriptors(ByteStream stream) {
- // TODO - read data memebers
+ mMidiStreamingClass = stream.unpackUsbShort();
stream.advance(mLength - stream.getReadCount());
return mLength;
}
@@ -42,6 +48,7 @@ public final class UsbMSMidiHeader extends UsbACInterface {
canvas.writeHeader(3, "MS Midi Header: " + ReportCanvas.getHexString(getType())
+ " SubType: " + ReportCanvas.getHexString(getSubclass())
- + " Length: " + getLength());
+ + " Length: " + getLength()
+ + " MidiStreamingClass :" + getMidiStreamingClass());
}
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java
new file mode 100644
index 000000000000..37bd0f8f6f7b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMidiBlockParser.java
@@ -0,0 +1,173 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ * A class to parse Block Descriptors
+ * see midi20.pdf section 5.4
+ */
+public class UsbMidiBlockParser {
+ private static final String TAG = "UsbMidiBlockParser";
+
+ // Block header size
+ public static final int MIDI_BLOCK_HEADER_SIZE = 5;
+ public static final int MIDI_BLOCK_SIZE = 13;
+ public static final int REQ_GET_DESCRIPTOR = 0x06;
+ public static final int CS_GR_TRM_BLOCK = 0x26; // Class-specific GR_TRM_BLK
+ public static final int GR_TRM_BLOCK_HEADER = 0x01; // Group block header
+ public static final int REQ_TIMEOUT_MS = 2000; // 2 second timeout
+ public static final int DEFAULT_MIDI_TYPE = 1; // Default MIDI type
+
+ protected int mHeaderLength; // 0:1 Size of header descriptor
+ protected int mHeaderDescriptorType; // 1:1 Descriptor Type
+ protected int mHeaderDescriptorSubtype; // 2:1 Descriptor Subtype
+ protected int mTotalLength; // 3:2 Total Length of header and blocks
+
+ static class GroupTerminalBlock {
+ protected int mLength; // 0:1 Size of descriptor
+ protected int mDescriptorType; // 1:1 Descriptor Type
+ protected int mDescriptorSubtype; // 2:1 Descriptor Subtype
+ protected int mGroupBlockId; // 3:1 Id of Group Terminal Block
+ protected int mGroupTerminalBlockType; // 4:1 bi-directional, IN, or OUT
+ protected int mGroupTerminal; // 5:1 Group Terminal Number
+ protected int mNumGroupTerminals; // 6:1 Number of Group Terminals
+ protected int mBlockItem; // 7:1 ID of STRING descriptor of Block item
+ protected int mMidiProtocol; // 8:1 MIDI protocol
+ protected int mMaxInputBandwidth; // 9:2 Max Input Bandwidth
+ protected int mMaxOutputBandwidth; // 11:2 Max Output Bandwidth
+
+ public int parseRawDescriptors(ByteStream stream) {
+ mLength = stream.getUnsignedByte();
+ mDescriptorType = stream.getUnsignedByte();
+ mDescriptorSubtype = stream.getUnsignedByte();
+ mGroupBlockId = stream.getUnsignedByte();
+ mGroupTerminalBlockType = stream.getUnsignedByte();
+ mGroupTerminal = stream.getUnsignedByte();
+ mNumGroupTerminals = stream.getUnsignedByte();
+ mBlockItem = stream.getUnsignedByte();
+ mMidiProtocol = stream.getUnsignedByte();
+ mMaxInputBandwidth = stream.unpackUsbShort();
+ mMaxOutputBandwidth = stream.unpackUsbShort();
+ return mLength;
+ }
+ }
+
+ private ArrayList<GroupTerminalBlock> mGroupTerminalBlocks =
+ new ArrayList<GroupTerminalBlock>();
+
+ public UsbMidiBlockParser() {
+ }
+
+ /**
+ * Parses a raw ByteStream into a block terminal descriptor.
+ * The header is parsed before each block is parsed.
+ * @param stream ByteStream to parse
+ * @return The total length that has been parsed.
+ */
+ public int parseRawDescriptors(ByteStream stream) {
+ mHeaderLength = stream.getUnsignedByte();
+ mHeaderDescriptorType = stream.getUnsignedByte();
+ mHeaderDescriptorSubtype = stream.getUnsignedByte();
+ mTotalLength = stream.unpackUsbShort();
+
+ while (stream.available() >= MIDI_BLOCK_SIZE) {
+ GroupTerminalBlock block = new GroupTerminalBlock();
+ block.parseRawDescriptors(stream);
+ mGroupTerminalBlocks.add(block);
+ }
+
+ return mTotalLength;
+ }
+
+ /**
+ * Calculates the MIDI type through querying the device twice, once for the size
+ * of the block descriptor and once for the block descriptor. This descriptor is
+ * then parsed to return the MIDI type.
+ * See the MIDI 2.0 USB doc for more info.
+ * @param connection UsbDeviceConnection to send the request
+ * @param interfaceNumber The interface number to query
+ * @param alternateInterfaceNumber The alternate interface of the interface
+ * @return The MIDI type as an int.
+ */
+ public int calculateMidiType(UsbDeviceConnection connection, int interfaceNumber,
+ int alternateInterfaceNumber) {
+ byte[] byteArray = new byte[MIDI_BLOCK_HEADER_SIZE];
+ try {
+ // This first request is simply to get the full size of the descriptor.
+ // This info is stored in the last two bytes of the header.
+ int rdo = connection.controlTransfer(
+ UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD
+ | UsbConstants.USB_CLASS_AUDIO,
+ REQ_GET_DESCRIPTOR,
+ (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber,
+ interfaceNumber,
+ byteArray,
+ MIDI_BLOCK_HEADER_SIZE,
+ REQ_TIMEOUT_MS);
+ if (rdo > 0) {
+ if (byteArray[1] != CS_GR_TRM_BLOCK) {
+ Log.e(TAG, "Incorrect descriptor type: " + byteArray[1]);
+ return DEFAULT_MIDI_TYPE;
+ }
+ if (byteArray[2] != GR_TRM_BLOCK_HEADER) {
+ Log.e(TAG, "Incorrect descriptor subtype: " + byteArray[2]);
+ return DEFAULT_MIDI_TYPE;
+ }
+ int newSize = (((int) byteArray[3]) & (0xff))
+ + ((((int) byteArray[4]) & (0xff)) << 8);
+ if (newSize <= 0) {
+ Log.e(TAG, "Parsed a non-positive block terminal size: " + newSize);
+ return DEFAULT_MIDI_TYPE;
+ }
+ byteArray = new byte[newSize];
+ rdo = connection.controlTransfer(
+ UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD
+ | UsbConstants.USB_CLASS_AUDIO,
+ REQ_GET_DESCRIPTOR,
+ (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber,
+ interfaceNumber,
+ byteArray,
+ newSize,
+ REQ_TIMEOUT_MS);
+ if (rdo > 0) {
+ ByteStream stream = new ByteStream(byteArray);
+ parseRawDescriptors(stream);
+ if (mGroupTerminalBlocks.isEmpty()) {
+ Log.e(TAG, "Group Terminal Blocks failed parsing: " + DEFAULT_MIDI_TYPE);
+ return DEFAULT_MIDI_TYPE;
+ } else {
+ Log.d(TAG, "MIDI protocol: " + mGroupTerminalBlocks.get(0).mMidiProtocol);
+ return mGroupTerminalBlocks.get(0).mMidiProtocol;
+ }
+ } else {
+ Log.e(TAG, "second transfer failed: " + rdo);
+ }
+ } else {
+ Log.e(TAG, "first transfer failed: " + rdo);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Can not communicate with USB device", e);
+ }
+ return DEFAULT_MIDI_TYPE;
+ }
+}