Rewrite MIDI 1.0 USB Host mode in Java
ALSA has multiple bugs related to MIDI 1.0. This CL removes
Android's dependency on ALSA for MIDI 1.0 USB Host mode. MIDI to
USB MIDI conversion is done in UsbMidiPacketConverter.java
ALSA is still used for peripheral mode.
Bug: 217392573
Test: Ran local unit tests with UsbMidiPacketConverter
Test: MidiScope/MidiKeyboard with multiple MIDI keyboards
Change-Id: I0b86f9bd26e4c9a50576f704deb6b53fc9a5c130
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 6e72479..046573d 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -62,6 +62,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -114,6 +115,19 @@
private final PackageManager mPackageManager;
+ private static final String MIDI_LEGACY_STRING = "MIDI 1.0";
+ private static final String MIDI_UNIVERSAL_STRING = "MIDI 2.0";
+
+ // Used to lock mUsbMidiLegacyDeviceOpenCount and mUsbMidiUniversalDeviceInUse.
+ private final Object mUsbMidiLock = new Object();
+
+ // Number of times a USB MIDI 1.0 device has opened, based on the device name.
+ private final HashMap<String, Integer> mUsbMidiLegacyDeviceOpenCount =
+ new HashMap<String, Integer>();
+
+ // Whether a USB MIDI device has opened, based on the device name.
+ private final HashSet<String> mUsbMidiUniversalDeviceInUse = new HashSet<String>();
+
// UID of BluetoothMidiService
private int mBluetoothServiceUid;
@@ -246,6 +260,12 @@
}
for (DeviceConnection connection : mDeviceConnections.values()) {
+ if (connection.getDevice().getDeviceInfo().getType()
+ == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
+ }
+ }
connection.getDevice().removeDeviceConnection(connection);
}
}
@@ -698,6 +718,7 @@
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()
@@ -734,6 +755,15 @@
}
}
+ if (deviceInfo.getType() == MidiDeviceInfo.TYPE_USB) {
+ synchronized (mUsbMidiLock) {
+ if (isUsbMidiDeviceInUseLocked(deviceInfo)) {
+ throw new IllegalArgumentException("device already in use: " + deviceInfo);
+ }
+ addUsbMidiDeviceLocked(deviceInfo);
+ }
+ }
+
// clear calling identity so bindService does not fail
final long identity = Binder.clearCallingIdentity();
try {
@@ -1145,4 +1175,82 @@
}
pw.decreaseIndent();
}
+
+ // hold mUsbMidiLock before calling this
+ private boolean isUsbMidiDeviceInUseLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return false;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ // Only one MIDI 2.0 device can be used at once.
+ // Multiple MIDI 1.0 devices can be used at once.
+ if (mUsbMidiUniversalDeviceInUse.contains(deviceName)
+ || ((tagName).equals(MIDI_UNIVERSAL_STRING)
+ && (mUsbMidiLegacyDeviceOpenCount.containsKey(deviceName)))) {
+ return true;
+ }
+ return false;
+ }
+
+ // hold mUsbMidiLock before calling this
+ void addUsbMidiDeviceLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
+ mUsbMidiUniversalDeviceInUse.add(deviceName);
+ } else if ((tagName).equals(MIDI_LEGACY_STRING)) {
+ int count = mUsbMidiLegacyDeviceOpenCount.getOrDefault(deviceName, 0) + 1;
+ mUsbMidiLegacyDeviceOpenCount.put(deviceName, count);
+ }
+ }
+
+ // hold mUsbMidiLock before calling this
+ void removeUsbMidiDeviceLocked(MidiDeviceInfo info) {
+ String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (name.length() < MIDI_LEGACY_STRING.length()) {
+ return;
+ }
+ String deviceName = extractUsbDeviceName(name);
+ String tagName = extractUsbDeviceTag(name);
+
+ if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
+ mUsbMidiUniversalDeviceInUse.remove(deviceName);
+ } else if ((tagName).equals(MIDI_LEGACY_STRING)) {
+ if (mUsbMidiLegacyDeviceOpenCount.containsKey(deviceName)) {
+ int count = mUsbMidiLegacyDeviceOpenCount.get(deviceName);
+ if (count > 1) {
+ mUsbMidiLegacyDeviceOpenCount.put(deviceName, count - 1);
+ } else {
+ mUsbMidiLegacyDeviceOpenCount.remove(deviceName);
+ }
+ }
+ }
+ }
+
+ // The USB property name is in the form "manufacturer product#Id MIDI 1.0".
+ // This is defined in UsbDirectMidiDevice.java.
+ // This function extracts out the "manufacturer product#Id " part.
+ // Two devices would have the same device name if they had the following property name:
+ // "manufacturer product#Id MIDI 1.0"
+ // "manufacturer product#Id MIDI 2.0"
+ // Note that MIDI_LEGACY_STRING and MIDI_UNIVERSAL_STRING are the same length.
+ String extractUsbDeviceName(String propertyName) {
+ return propertyName.substring(0, propertyName.length() - MIDI_LEGACY_STRING.length());
+ }
+
+ // The USB property name is in the form "manufacturer product#Id MIDI 1.0".
+ // This is defined in UsbDirectMidiDevice.java.
+ // This function extracts the "MIDI 1.0" part.
+ // Note that MIDI_LEGACY_STRING and MIDI_UNIVERSAL_STRING are the same length.
+ String extractUsbDeviceTag(String propertyName) {
+ return propertyName.substring(propertyName.length() - MIDI_LEGACY_STRING.length());
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 1c72eb8..6d934a9 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -36,7 +36,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
/**
@@ -107,12 +106,6 @@
return false;
}
- /**
- * List of connected MIDI devices
- */
- private final HashMap<String, UsbMidiDevice>
- mMidiDevices = new HashMap<String, UsbMidiDevice>();
-
// UsbMidiDevice for USB peripheral mode (gadget) device
private UsbMidiDevice mPeripheralMidiDevice = null;
@@ -255,45 +248,6 @@
}
}
- // look for MIDI devices
- boolean hasMidi = parser.hasMIDIInterface();
- 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;
- Bundle properties = new Bundle();
- String manufacturer = usbDevice.getManufacturerName();
- String product = usbDevice.getProductName();
- String version = usbDevice.getVersion();
- String name;
- if (manufacturer == null || manufacturer.isEmpty()) {
- name = product;
- } else if (product == null || product.isEmpty()) {
- name = manufacturer;
- } else {
- name = manufacturer + " " + product;
- }
- 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,
- usbDevice.getSerialNumber());
- properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
- properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
- properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
-
- UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
- cardRec.getCardNum(), 0 /*device*/, midiNumInputs, midiNumOutputs);
- if (usbMidiDevice != null) {
- mMidiDevices.put(deviceAddress, usbMidiDevice);
- }
- }
-
logDevices("deviceAdded()");
if (DEBUG) {
@@ -314,13 +268,6 @@
selectDefaultDevice(); // if there any external devices left, select one of them
}
- // MIDI
- UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
- if (usbMidiDevice != null) {
- Slog.i(TAG, "USB MIDI Device Removed: " + usbMidiDevice);
- IoUtils.closeQuietly(usbMidiDevice);
- }
-
logDevices("usbDeviceRemoved()");
}
@@ -376,12 +323,6 @@
usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
}
- for (String deviceAddr : mMidiDevices.keySet()) {
- // A UsbMidiDevice does not have a handle to the UsbDevice anymore
- mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
- UsbAlsaManagerProto.MIDI_DEVICES);
- }
-
dump.end(token);
}
diff --git a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
similarity index 65%
rename from services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
rename to services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
index 13d404c..0fa79df 100644
--- a/services/usb/java/com/android/server/usb/UsbUniversalMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java
@@ -20,6 +20,7 @@
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceServer;
@@ -43,16 +44,20 @@
import java.util.ArrayList;
/**
- * A MIDI device that opens device connections to MIDI 2.0 endpoints.
+ * Opens device connections to MIDI 1.0 or MIDI 2.0 endpoints.
+ * This endpoint will not use ALSA and opens a UsbDeviceConnection directly.
*/
-public final class UsbUniversalMidiDevice implements Closeable {
- private static final String TAG = "UsbUniversalMidiDevice";
+public final class UsbDirectMidiDevice implements Closeable {
+ private static final String TAG = "UsbDirectMidiDevice";
private static final boolean DEBUG = false;
private Context mContext;
private UsbDevice mUsbDevice;
private UsbDescriptorParser mParser;
private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces;
+ private final boolean mIsUniversalMidiDevice;
+ private final String mUniqueUsbDeviceIdentifier;
+ private final boolean mShouldCallSetInterface;
// USB outputs are MIDI inputs
private final InputReceiverProxy[] mMidiInputPortReceivers;
@@ -64,6 +69,11 @@
// event schedulers for each input port of the physical device
private MidiEventScheduler[] mEventSchedulers;
+ // Arbitrary number for timeout to not continue sending/receiving number from
+ // an inactive device. This number tries to balances the number of cycles and
+ // not being permanently stuck.
+ private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 100;
+
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
@@ -74,6 +84,8 @@
private final Object mLock = new Object();
private boolean mIsOpen;
+ private final UsbMidiPacketConverter mUsbMidiPacketConverter = new UsbMidiPacketConverter();
+
private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
@Override
@@ -140,13 +152,15 @@
}
/**
- * Creates an UsbUniversalMidiDevice based on the input parameters. Read/Write streams
+ * Creates an UsbDirectMidiDevice 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);
+ public static UsbDirectMidiDevice create(Context context, UsbDevice usbDevice,
+ UsbDescriptorParser parser, boolean isUniversalMidiDevice,
+ String uniqueUsbDeviceIdentifier) {
+ UsbDirectMidiDevice midiDevice = new UsbDirectMidiDevice(usbDevice, parser,
+ isUniversalMidiDevice, uniqueUsbDeviceIdentifier);
if (!midiDevice.register(context)) {
IoUtils.closeQuietly(midiDevice);
Log.e(TAG, "createDeviceServer failed");
@@ -155,11 +169,22 @@
return midiDevice;
}
- private UsbUniversalMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser) {
+ private UsbDirectMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser,
+ boolean isUniversalMidiDevice, String uniqueUsbDeviceIdentifier) {
mUsbDevice = usbDevice;
mParser = parser;
+ mUniqueUsbDeviceIdentifier = uniqueUsbDeviceIdentifier;
+ mIsUniversalMidiDevice = isUniversalMidiDevice;
- mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
+ // Set interface should only be called when alternate interfaces exist.
+ // Otherwise, USB devices may not handle this gracefully.
+ mShouldCallSetInterface = (parser.calculateMidiInterfaceDescriptorsCount() > 1);
+
+ if (isUniversalMidiDevice) {
+ mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
+ } else {
+ mUsbInterfaces = parser.findLegacyMidiInterfaceDescriptors();
+ }
int numInputs = 0;
int numOutputs = 0;
@@ -182,8 +207,8 @@
mNumInputs = numInputs;
mNumOutputs = numOutputs;
- Log.d(TAG, "Created UsbUniversalMidiDevice with " + numInputs + " inputs and "
- + numOutputs + " outputs");
+ Log.d(TAG, "Created UsbDirectMidiDevice with " + numInputs + " inputs and "
+ + numOutputs + " outputs. isUniversalMidiDevice: " + isUniversalMidiDevice);
// Create MIDI port receivers based on the number of output ports. The
// output of USB is the input of MIDI.
@@ -218,23 +243,25 @@
if (doesInterfaceContainInput
&& doesInterfaceContainOutput) {
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
-
- // The ALSA does not handle switching to the MIDI 2.0 interface correctly
- // and stops exposing /dev/snd/midiC1D0 after calling connection.setInterface().
- // Thus, simply use the control interface (interface zero).
+ UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
+ if (!updateUsbInterface(usbInterface, connection)) {
+ continue;
+ }
int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
- 0,
+ interfaceDescriptor.getInterfaceNumber(),
interfaceDescriptor.getAlternateSetting());
+
connection.close();
return defaultMidiProtocol;
}
}
- Log.d(TAG, "Cannot find interface with both input and output endpoints");
+ Log.w(TAG, "Cannot find interface with both input and output endpoints");
return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
}
private boolean openLocked() {
+ Log.d(TAG, "openLocked()");
UsbManager manager = mContext.getSystemService(UsbManager.class);
mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size());
@@ -258,8 +285,10 @@
}
if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
- connection.setInterface(interfaceDescriptor.toAndroid(mParser));
- connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true);
+ UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
+ if (!updateUsbInterface(usbInterface, connection)) {
+ continue;
+ }
mUsbDeviceConnections.add(connection);
mInputUsbEndpoints.add(inputEndpoints);
mOutputUsbEndpoints.add(outputEndpoints);
@@ -283,14 +312,17 @@
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;
+ final UsbDeviceConnection connectionFinal =
+ mUsbDeviceConnections.get(connectionIndex);
+ final UsbEndpoint endpointFinal =
+ mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
+ final int portFinal = portNumber;
- new Thread("UsbUniversalMidiDevice input thread " + portF) {
+ new Thread("UsbDirectMidiDevice input thread " + portFinal) {
@Override
public void run() {
- byte[] inputBuffer = new byte[epF.getMaxPacketSize()];
+ byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
+ Log.d(TAG, "input buffer size: " + inputBuffer.length);
try {
while (true) {
// Record time of event immediately after waking.
@@ -298,20 +330,34 @@
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);
+ int nRead = connectionFinal.bulkTransfer(endpointFinal,
+ inputBuffer, inputBuffer.length,
+ BULK_TRANSFER_TIMEOUT_MILLISECONDS);
if (nRead > 0) {
if (DEBUG) {
- logByteArray("Input ", inputBuffer, 0,
- nRead);
+ logByteArray("Input before conversion ", inputBuffer,
+ 0, nRead);
}
- outputReceivers[portF].send(inputBuffer, 0, nRead,
- timestamp);
+ byte[] convertedArray;
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ convertedArray = swapEndiannessPerWord(inputBuffer,
+ nRead);
+ } else {
+ convertedArray =
+ mUsbMidiPacketConverter.usbMidiToRawMidi(
+ inputBuffer, nRead);
+ }
+
+ if (DEBUG) {
+ logByteArray("Input after conversion ", convertedArray,
+ 0, convertedArray.length);
+ }
+
+ outputReceivers[portFinal].send(convertedArray, 0,
+ convertedArray.length, timestamp);
}
}
}
@@ -333,19 +379,20 @@
for (int endpointIndex = 0;
endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
endpointIndex++) {
- final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
- final UsbEndpoint epF =
+ final UsbDeviceConnection connectionFinal =
+ mUsbDeviceConnections.get(connectionIndex);
+ final UsbEndpoint endpointFinal =
mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
- final int portF = portNumber;
- final MidiEventScheduler eventSchedulerF = mEventSchedulers[portF];
+ final int portFinal = portNumber;
+ final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal];
- new Thread("UsbUniversalMidiDevice output thread " + portF) {
+ new Thread("UsbDirectMidiDevice output thread " + portFinal) {
@Override
public void run() {
while (true) {
MidiEvent event;
try {
- event = (MidiEvent) eventSchedulerF.waitNextEvent();
+ event = (MidiEvent) eventSchedulerFinal.waitNextEvent();
} catch (InterruptedException e) {
// try again
continue;
@@ -354,16 +401,32 @@
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,
+ logByteArray("Output before conversion ", event.data, 0,
event.count);
}
- connectionF.bulkTransfer(epF, event.data, event.count, 0);
- eventSchedulerF.addEventToPool(event);
+
+ byte[] convertedArray;
+ if (mIsUniversalMidiDevice) {
+ // For USB, each 32 bit word of a UMP is
+ // sent with the least significant byte first.
+ convertedArray = swapEndiannessPerWord(event.data,
+ event.count);
+ } else {
+ convertedArray =
+ mUsbMidiPacketConverter.rawMidiToUsbMidi(
+ event.data, event.count);
+ }
+
+ if (DEBUG) {
+ logByteArray("Output after conversion ", convertedArray, 0,
+ convertedArray.length);
+ }
+
+ connectionFinal.bulkTransfer(endpointFinal, convertedArray,
+ convertedArray.length,
+ BULK_TRANSFER_TIMEOUT_MILLISECONDS);
+ eventSchedulerFinal.addEventToPool(event);
}
Log.d(TAG, "output thread exit");
}
@@ -381,11 +444,15 @@
mContext = context;
MidiManager midiManager = context.getSystemService(MidiManager.class);
if (midiManager == null) {
- Log.e(TAG, "No MidiManager in UsbUniversalMidiDevice.create()");
+ Log.e(TAG, "No MidiManager in UsbDirectMidiDevice.create()");
return false;
}
- mDefaultMidiProtocol = calculateDefaultMidiProtocol();
+ if (mIsUniversalMidiDevice) {
+ mDefaultMidiProtocol = calculateDefaultMidiProtocol();
+ } else {
+ mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UNKNOWN;
+ }
Bundle properties = new Bundle();
String manufacturer = mUsbDevice.getManufacturerName();
@@ -397,8 +464,15 @@
} else if (product == null || product.isEmpty()) {
name = manufacturer;
} else {
- name = manufacturer + " " + product + " MIDI 2.0";
+ name = manufacturer + " " + product;
}
+ name += "#" + mUniqueUsbDeviceIdentifier;
+ if (mIsUniversalMidiDevice) {
+ name += " MIDI 2.0";
+ } else {
+ name += " MIDI 1.0";
+ }
+ Log.e(TAG, name);
properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
@@ -430,10 +504,13 @@
}
private void closeLocked() {
+ Log.d(TAG, "closeLocked()");
for (int i = 0; i < mEventSchedulers.length; i++) {
mMidiInputPortReceivers[i].setReceiver(null);
mEventSchedulers[i].close();
}
+ mEventSchedulers = null;
+
for (UsbDeviceConnection connection : mUsbDeviceConnections) {
connection.close();
}
@@ -444,15 +521,19 @@
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 byte[] swapEndiannessPerWord(byte[] inputArray, int size) {
+ int numberOfExcessBytes = size & 3;
+ if (numberOfExcessBytes != 0) {
+ Log.e(TAG, "size not multiple of 4: " + size);
}
+ byte[] outputArray = new byte[size - numberOfExcessBytes];
+ for (int i = 0; i + 3 < size; i += 4) {
+ outputArray[i] = inputArray[i + 3];
+ outputArray[i + 1] = inputArray[i + 2];
+ outputArray[i + 2] = inputArray[i + 1];
+ outputArray[i + 3] = inputArray[i];
+ }
+ return outputArray;
}
private static void logByteArray(String prefix, byte[] value, int offset, int count) {
@@ -465,4 +546,24 @@
}
Log.d(TAG, builder.toString());
}
+
+ private boolean updateUsbInterface(UsbInterface usbInterface,
+ UsbDeviceConnection connection) {
+ if (usbInterface == null) {
+ Log.e(TAG, "Usb Interface is null");
+ return false;
+ }
+ if (!connection.claimInterface(usbInterface, true)) {
+ Log.e(TAG, "Can't claim interface");
+ return false;
+ }
+ if (mShouldCallSetInterface) {
+ if (!connection.setInterface(usbInterface)) {
+ Log.w(TAG, "Can't set interface");
+ }
+ } else {
+ Log.w(TAG, "no alternate interface");
+ }
+ return true;
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 9ac270f..f09b61b 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -50,9 +50,12 @@
import libcore.io.IoUtils;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
+import java.util.Random;
/**
* UsbHostManager manages USB state in host mode.
@@ -94,10 +97,12 @@
private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>();
/**
- * List of connected MIDI devices
+ * List of connected MIDI devices. Key on deviceAddress.
*/
- private final HashMap<String, UsbUniversalMidiDevice>
- mMidiDevices = new HashMap<String, UsbUniversalMidiDevice>();
+ private final HashMap<String, ArrayList<UsbDirectMidiDevice>>
+ mMidiDevices = new HashMap<String, ArrayList<UsbDirectMidiDevice>>();
+ private final HashSet<String> mMidiUniqueCodes = new HashSet<String>();
+ private final Random mRandom = new Random();
private final boolean mHasMidiFeature;
/*
@@ -425,15 +430,35 @@
mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
if (mHasMidiFeature) {
+ // Use a 3 digit code to associate MIDI devices with one another.
+ // Each MIDI device already has mId for uniqueness. mId is generated
+ // sequentially. For clarity, this code is not generated sequentially.
+ String uniqueUsbDeviceIdentifier = generateNewUsbDeviceIdentifier();
+
+ ArrayList<UsbDirectMidiDevice> midiDevices =
+ new ArrayList<UsbDirectMidiDevice>();
if (parser.containsUniversalMidiDeviceEndpoint()) {
- UsbUniversalMidiDevice midiDevice = UsbUniversalMidiDevice.create(mContext,
- newDevice, parser);
+ UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
+ newDevice, parser, true, uniqueUsbDeviceIdentifier);
if (midiDevice != null) {
- mMidiDevices.put(deviceAddress, midiDevice);
+ midiDevices.add(midiDevice);
} else {
Slog.e(TAG, "Universal Midi Device is null.");
}
}
+ if (parser.containsLegacyMidiDeviceEndpoint()) {
+ UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
+ newDevice, parser, false, uniqueUsbDeviceIdentifier);
+ if (midiDevice != null) {
+ midiDevices.add(midiDevice);
+ } else {
+ Slog.e(TAG, "Legacy Midi Device is null.");
+ }
+ }
+
+ if (!midiDevices.isEmpty()) {
+ mMidiDevices.put(deviceAddress, midiDevices);
+ }
}
// Tracking
@@ -471,10 +496,13 @@
mPermissionManager.usbDeviceRemoved(device);
// MIDI
- UsbUniversalMidiDevice midiDevice = mMidiDevices.remove(deviceAddress);
- if (midiDevice != null) {
- Slog.i(TAG, "USB Universal MIDI Device Removed: " + deviceAddress);
- IoUtils.closeQuietly(midiDevice);
+ ArrayList<UsbDirectMidiDevice> midiDevices =
+ mMidiDevices.remove(deviceAddress);
+ for (UsbDirectMidiDevice midiDevice : midiDevices) {
+ if (midiDevice != null) {
+ Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
+ IoUtils.closeQuietly(midiDevice);
+ }
}
getCurrentUserSettings().usbDeviceRemoved(device);
@@ -608,6 +636,19 @@
return true;
}
+ // Generate a 3 digit code.
+ private String generateNewUsbDeviceIdentifier() {
+ String code;
+ do {
+ code = "";
+ for (int i = 0; i < 3; i++) {
+ code += mRandom.nextInt(10);
+ }
+ } while (mMidiUniqueCodes.contains(code));
+ mMidiUniqueCodes.add(code);
+ return code;
+ }
+
private native void monitorUsbHostBus();
private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
}
diff --git a/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
new file mode 100644
index 0000000..7c93c76
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2022 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 java.io.ByteArrayOutputStream;
+
+/**
+ * Converts between MIDI packets and USB MIDI 1.0 packets.
+ */
+public class UsbMidiPacketConverter {
+
+ // Refer to Table 4-1 in USB MIDI 1.0 spec.
+ private static final int[] PAYLOAD_SIZE = new int[]{
+ /* 0x00 */ -1, // Miscellaneous function codes. Reserved for future extensions.
+ /* 0x01 */ -1, // Cable events. Reserved for future expansion.
+ /* 0x02 */ 2, // Two-byte System Common messages like MTC, SongSelect, etc
+ /* 0x03 */ 3, // Three-byte System Common messages like SPP, etc.
+ /* 0x04 */ 3, // SysEx starts or continues
+ /* 0x05 */ 1, // Single-byte System Common Message or single-byte SysEx ends.
+ /* 0x06 */ 2, // SysEx ends with following two bytes.
+ /* 0x07 */ 3, // SysEx ends with following three bytes.
+ /* 0x08 */ 3, // Note-off
+ /* 0x09 */ 3, // Note-on
+ /* 0x0a */ 3, // Poly-KeyPress
+ /* 0x0b */ 3, // Control Change
+ /* 0x0c */ 2, // Program Change
+ /* 0x0d */ 2, // Channel Pressure
+ /* 0x0e */ 3, // PitchBend Change
+ /* 0x0f */ 1 // Single Byte
+ };
+
+ // Each System MIDI message is a certain size. These can be mapped to a
+ // Code Index number defined in Table 4-1 of USB MIDI 1.0.
+ private static final int[] CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE = new int[]{
+ /* 0x00 */ -1, // Start of Exclusive. Special case.
+ /* 0x01 */ 2, // MIDI Time Code. Two byte message
+ /* 0x02 */ 3, // Song Point Pointer. Three byte message
+ /* 0x03 */ 2, // Song Select. Two byte message
+ /* 0x04 */ -1, // Undefined MIDI System Common
+ /* 0x05 */ -1, // Undefined MIDI System Common
+ /* 0x06 */ 5, // Tune Request. One byte message
+ /* 0x07 */ -1, // End of Exclusive. Special case.
+ /* 0x08 */ 5, // Timing clock. One byte message
+ /* 0x09 */ -1, // Undefined MIDI System Real-time
+ /* 0x0a */ 5, // Start. One byte message
+ /* 0x0b */ 5, // Continue. One byte message
+ /* 0x0c */ 5, // Stop. One byte message
+ /* 0x0d */ -1, // Undefined MIDI System Real-time
+ /* 0x0e */ 5, // Active Sensing. One byte message
+ /* 0x0f */ 5 // System Reset. One byte message
+ };
+
+ // These code index numbers also come from Table 4-1 in USB MIDI 1.0 spec.
+ private static final byte CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES = 0x4;
+ private static final byte CODE_INDEX_NUMBER_SINGLE_BYTE = 0xF;
+ private static final byte CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE = (byte) 0x5;
+
+ // System messages are defined in MIDI.
+ private static final byte FIRST_SYSTEM_MESSAGE_VALUE = (byte) 0xF0;
+ private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
+ private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
+
+ private UsbMidiEncoder mUsbMidiEncoder = new UsbMidiEncoder();
+ private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
+
+ /**
+ * Converts a USB MIDI array into a raw MIDI array.
+ *
+ * @param usbMidiBytes the USB MIDI bytes to convert
+ * @param size the size of usbMidiBytes
+ * @return byte array of raw MIDI packets
+ */
+ public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
+ return mUsbMidiDecoder.decode(usbMidiBytes, size);
+ }
+
+ /**
+ * Converts a raw MIDI array into a USB MIDI array.
+ *
+ * @param midiBytes the raw MIDI bytes to convert
+ * @param size the size of usbMidiBytes
+ * @return byte array of USB MIDI packets
+ */
+ public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size) {
+ return mUsbMidiEncoder.encode(midiBytes, size);
+ }
+
+ private class UsbMidiDecoder {
+ // Decodes the data from USB MIDI to raw MIDI.
+ // Each valid 4 byte input maps to a 1-3 byte output.
+ // Reference the USB MIDI 1.0 spec for more info.
+ public byte[] decode(byte[] usbMidiBytes, int size) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ for (int i = 0; i + 3 < size; i += 4) {
+ int codeIndex = usbMidiBytes[i] & 0x0f;
+ int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
+ if (numPayloadBytes < 0) {
+ continue;
+ }
+ outputStream.write(usbMidiBytes, i + 1, numPayloadBytes);
+ }
+ return outputStream.toByteArray();
+ }
+ }
+
+ private class UsbMidiEncoder {
+ // In order to facilitate large scale transfers, SysEx can be sent in multiple packets.
+ // If encode() is called without an SysEx end, we must continue SysEx for the next packet.
+ // All other packets should be 3 bytes or less and must be not be broken between packets.
+ private byte[] mStoredSystemExclusiveBytes = new byte[3];
+ private int mNumStoredSystemExclusiveBytes = 0;
+ private boolean mHasSystemExclusiveStarted = false;
+
+ private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
+
+ // Encodes the data from raw MIDI to USB MIDI.
+ // Each valid 1-3 byte input maps to a 4 byte output.
+ // Reference the USB MIDI 1.0 spec for more info.
+ // MidiFramer is not needed here as this code handles partial packets.
+ // Long SysEx messages split between packets will encode and return a
+ // byte stream even if the SysEx end has not been sent.
+ // If there are less than 3 remaining data bytes in a SysEx message left,
+ // these bytes will be combined with the next set of packets.
+ public byte[] encode(byte[] midiBytes, int size) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ int curLocation = 0;
+ while (curLocation < size) {
+ if (midiBytes[curLocation] >= 0) { // Data byte
+ if (mHasSystemExclusiveStarted) {
+ mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
+ midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes++;
+ if (mNumStoredSystemExclusiveBytes == 3) {
+ outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES);
+ outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
+ mNumStoredSystemExclusiveBytes = 0;
+ }
+ } else {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ }
+ curLocation++;
+ continue;
+ } else if (midiBytes[curLocation] != SYSEX_END_EXCLUSIVE) {
+ // SysEx operation was interrupted. Pass the data directly down.
+ if (mHasSystemExclusiveStarted) {
+ int index = 0;
+ while (index < mNumStoredSystemExclusiveBytes) {
+ writeSingleByte(outputStream, mStoredSystemExclusiveBytes[index]);
+ index++;
+ }
+ mNumStoredSystemExclusiveBytes = 0;
+ mHasSystemExclusiveStarted = false;
+ }
+ }
+
+ if (midiBytes[curLocation] < FIRST_SYSTEM_MESSAGE_VALUE) { // Channel message
+ byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
+ int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
+ if (curLocation + channelMessageSize <= size) {
+ outputStream.write(codeIndexNumber);
+ outputStream.write(midiBytes, curLocation, channelMessageSize);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
+ curLocation += channelMessageSize;
+ } else { // The packet is missing data. Use single byte messages.
+ while (curLocation < size) {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ }
+ }
+ } else if (midiBytes[curLocation] == SYSEX_START_EXCLUSIVE) {
+ mHasSystemExclusiveStarted = true;
+ mStoredSystemExclusiveBytes[0] = midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes = 1;
+ curLocation++;
+ } else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
+ // 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
+ outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
+ + mNumStoredSystemExclusiveBytes);
+ mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
+ midiBytes[curLocation];
+ mNumStoredSystemExclusiveBytes++;
+ outputStream.write(mStoredSystemExclusiveBytes, 0,
+ mNumStoredSystemExclusiveBytes);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - mNumStoredSystemExclusiveBytes);
+ mHasSystemExclusiveStarted = false;
+ mNumStoredSystemExclusiveBytes = 0;
+ curLocation++;
+ } else {
+ int systemType = midiBytes[curLocation] & 0x0f;
+ int codeIndexNumber = CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE[systemType];
+ if (codeIndexNumber < 0) { // Unknown type. Use single byte messages.
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ } else {
+ int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
+ if (curLocation + systemMessageSize <= size) {
+ outputStream.write(codeIndexNumber);
+ outputStream.write(midiBytes, curLocation, systemMessageSize);
+ // Fill in the rest of the bytes with 0.
+ outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
+ curLocation += systemMessageSize;
+ } else { // The packet is missing data. Use single byte messages.
+ while (curLocation < size) {
+ writeSingleByte(outputStream, midiBytes[curLocation]);
+ curLocation++;
+ }
+ }
+ }
+ }
+ }
+ return outputStream.toByteArray();
+ }
+
+ private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
+ outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE);
+ outputStream.write(byteToWrite);
+ outputStream.write(0);
+ outputStream.write(0);
+ }
+ }
+}
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 3412a6f..9f2fef2 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -198,14 +198,20 @@
if (mCurInterfaceDescriptor != null) {
switch (mCurInterfaceDescriptor.getUsbClass()) {
case UsbDescriptor.CLASSID_AUDIO:
- descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
+ descriptor =
+ UsbACInterface.allocDescriptor(this, stream, length, type);
+ if (descriptor instanceof UsbMSMidiHeader) {
+ mCurInterfaceDescriptor.setMidiHeaderInterfaceDescriptor(
+ descriptor);
+ }
break;
case UsbDescriptor.CLASSID_VIDEO:
if (DEBUG) {
Log.d(TAG, " UsbDescriptor.CLASSID_VIDEO");
}
- descriptor = UsbVCInterface.allocDescriptor(this, stream, length, type);
+ descriptor =
+ UsbVCInterface.allocDescriptor(this, stream, length, type);
break;
case UsbDescriptor.CLASSID_AUDIOVIDEO:
@@ -218,7 +224,6 @@
Log.w(TAG, " Unparsed Class-specific");
break;
}
- mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor);
}
break;
@@ -674,6 +679,23 @@
public boolean containsUniversalMidiDeviceEndpoint() {
ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
findUniversalMidiInterfaceDescriptors();
+ return doesInterfaceContainEndpoint(interfaceDescriptors);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean containsLegacyMidiDeviceEndpoint() {
+ ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
+ findLegacyMidiInterfaceDescriptors();
+ return doesInterfaceContainEndpoint(interfaceDescriptors);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean doesInterfaceContainEndpoint(
+ ArrayList<UsbInterfaceDescriptor> interfaceDescriptors) {
int outputCount = 0;
int inputCount = 0;
for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size();
@@ -698,10 +720,24 @@
* @hide
*/
public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() {
+ return findMidiInterfaceDescriptors(MS_MIDI_2_0);
+ }
+
+ /**
+ * @hide
+ */
+ public ArrayList<UsbInterfaceDescriptor> findLegacyMidiInterfaceDescriptors() {
+ return findMidiInterfaceDescriptors(MS_MIDI_1_0);
+ }
+
+ /**
+ * @hide
+ */
+ private ArrayList<UsbInterfaceDescriptor> findMidiInterfaceDescriptors(int type) {
int count = 0;
ArrayList<UsbDescriptor> descriptors =
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
- ArrayList<UsbInterfaceDescriptor> universalMidiInterfaces =
+ ArrayList<UsbInterfaceDescriptor> midiInterfaces =
new ArrayList<UsbInterfaceDescriptor>();
for (UsbDescriptor descriptor : descriptors) {
@@ -709,14 +745,14 @@
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) {
+ UsbDescriptor midiHeaderDescriptor =
+ interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
+ if (midiHeaderDescriptor != null) {
+ if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
UsbMSMidiHeader midiHeader =
- (UsbMSMidiHeader) classSpecificDescriptor;
- if (midiHeader.getMidiStreamingClass() == MS_MIDI_2_0) {
- universalMidiInterfaces.add(interfaceDescriptor);
+ (UsbMSMidiHeader) midiHeaderDescriptor;
+ if (midiHeader.getMidiStreamingClass() == type) {
+ midiInterfaces.add(interfaceDescriptor);
}
}
}
@@ -726,10 +762,13 @@
+ " t:0x" + Integer.toHexString(descriptor.getType()));
}
}
- return universalMidiInterfaces;
+ return midiInterfaces;
}
- private int calculateNumLegacyMidiPorts(boolean isOutput) {
+ /**
+ * @hide
+ */
+ public int calculateMidiInterfaceDescriptorsCount() {
int count = 0;
ArrayList<UsbDescriptor> descriptors =
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
@@ -738,22 +777,12 @@
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) {
+ UsbDescriptor midiHeaderDescriptor =
+ interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
+ if (midiHeaderDescriptor != null) {
+ if (midiHeaderDescriptor 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) {
+ (UsbMSMidiHeader) midiHeaderDescriptor;
count++;
}
}
@@ -769,20 +798,6 @@
/**
* @hide
*/
- public int calculateNumLegacyMidiInputs() {
- return calculateNumLegacyMidiPorts(false /*isOutput*/);
- }
-
- /**
- * @hide
- */
- public int calculateNumLegacyMidiOutputs() {
- return calculateNumLegacyMidiPorts(true /*isOutput*/);
- }
-
- /**
- * @hide
- */
public float getInputHeadsetProbability() {
if (hasMIDIInterface()) {
return 0.0f;
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 ca4613b..9ddcb10 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -42,7 +42,8 @@
private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
new ArrayList<UsbEndpointDescriptor>();
- private UsbDescriptor mClassSpecificInterfaceDescriptor;
+ // Used for MIDI only.
+ private UsbDescriptor mMidiHeaderInterfaceDescriptor;
UsbInterfaceDescriptor(int length, byte type) {
super(length, type);
@@ -107,12 +108,12 @@
mEndpointDescriptors.add(endpoint);
}
- public void setClassSpecificInterfaceDescriptor(UsbDescriptor descriptor) {
- mClassSpecificInterfaceDescriptor = descriptor;
+ public void setMidiHeaderInterfaceDescriptor(UsbDescriptor descriptor) {
+ mMidiHeaderInterfaceDescriptor = descriptor;
}
- public UsbDescriptor getClassSpecificInterfaceDescriptor() {
- return mClassSpecificInterfaceDescriptor;
+ public UsbDescriptor getMidiHeaderInterfaceDescriptor() {
+ return mMidiHeaderInterfaceDescriptor;
}
/**