Merge "Add MIDI 2.0 host mode support"
This commit is contained in:
@@ -25710,6 +25710,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();
|
||||
@@ -25726,6 +25727,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
|
||||
@@ -25767,10 +25776,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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
@@ -53,6 +57,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}.
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -38,6 +45,39 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
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;
|
||||
|
||||
@@ -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<MidiDeviceInfo> 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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -61,6 +61,78 @@ enum {
|
||||
AMIDI_DEVICE_TYPE_BLUETOOTH = 3 /* A MIDI device connected via BlueTooth */
|
||||
};
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ LIBAMIDI {
|
||||
global:
|
||||
AMidiDevice_fromJava;
|
||||
AMidiDevice_release;
|
||||
AMidiDevice_getDefaultProtocol; # introduced=Tiramisu
|
||||
AMidiDevice_getType;
|
||||
AMidiDevice_getNumInputPorts;
|
||||
AMidiDevice_getNumOutputPorts;
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -643,13 +643,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -718,7 +736,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");
|
||||
@@ -728,7 +746,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,11 +816,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 {
|
||||
@@ -988,10 +1008,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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 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 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_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*/);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user