Merge "Rewrite MIDI 1.0 USB Host mode in Java"

This commit is contained in:
Robert Wu
2022-02-17 16:21:07 +00:00
committed by Android (Google) Code Review
7 changed files with 618 additions and 174 deletions

View File

@@ -63,6 +63,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -128,6 +129,19 @@ public class MidiService extends IMidiManager.Stub {
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;
@@ -271,6 +285,12 @@ public class MidiService extends IMidiManager.Stub {
}
for (DeviceConnection connection : mDeviceConnections.values()) {
if (connection.getDevice().getDeviceInfo().getType()
== MidiDeviceInfo.TYPE_USB) {
synchronized (mUsbMidiLock) {
removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
}
}
connection.getDevice().removeDeviceConnection(connection);
}
}
@@ -748,6 +768,7 @@ public class MidiService extends IMidiManager.Stub {
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()
@@ -784,6 +805,15 @@ public class MidiService extends IMidiManager.Stub {
}
}
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 {
@@ -1216,4 +1246,82 @@ public class MidiService extends IMidiManager.Stub {
}
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());
}
}

View File

@@ -36,7 +36,6 @@ import libcore.io.IoUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
@@ -107,12 +106,6 @@ public final class UsbAlsaManager {
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;
@@ -256,45 +249,6 @@ public final class UsbAlsaManager {
}
}
// 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) {
@@ -315,13 +269,6 @@ public final class UsbAlsaManager {
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()");
}
@@ -377,12 +324,6 @@ public final class UsbAlsaManager {
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);
}

View File

@@ -20,6 +20,7 @@ import android.content.Context;
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.io.IOException;
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
// 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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
}
/**
* 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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
}
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
} 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 @@ public final class UsbUniversalMidiDevice implements Closeable {
}
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
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 @@ public final class UsbUniversalMidiDevice implements Closeable {
}
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;
}
}

View File

@@ -50,9 +50,12 @@ import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
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 @@ public class UsbHostManager {
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;
/*
@@ -423,15 +428,35 @@ public class UsbHostManager {
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
@@ -469,10 +494,13 @@ public class UsbHostManager {
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);
@@ -606,6 +634,19 @@ public class UsbHostManager {
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);
}

View File

@@ -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);
}
}
}

View File

@@ -198,14 +198,20 @@ public final class UsbDescriptorParser {
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 @@ public final class UsbDescriptorParser {
Log.w(TAG, " Unparsed Class-specific");
break;
}
mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor);
}
break;
@@ -674,6 +679,23 @@ public final class UsbDescriptorParser {
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 @@ public final class UsbDescriptorParser {
* @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 @@ public final class UsbDescriptorParser {
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 @@ public final class UsbDescriptorParser {
+ " 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 @@ public final class UsbDescriptorParser {
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++;
}
}
@@ -766,20 +795,6 @@ public final class UsbDescriptorParser {
return count;
}
/**
* @hide
*/
public int calculateNumLegacyMidiInputs() {
return calculateNumLegacyMidiPorts(false /*isOutput*/);
}
/**
* @hide
*/
public int calculateNumLegacyMidiOutputs() {
return calculateNumLegacyMidiPorts(true /*isOutput*/);
}
/**
* @hide
*/

View File

@@ -42,7 +42,8 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
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 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
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;
}
/**