Add MIDI 2.0 host mode support
Adding MIDI 2.0 host mode support to Android. MIDI 2.0 peripherals create an alternative setting for Universal Midi Packets (UMP). UMP packets can be passed directly through as the host has full control. Instead of going through the ALSA, UMP packets can be sent directly through a UsbDeviceConnection. This CL also adds public apis to expose whether the pipe supports UMP. This for MidiDeviceInfo in Java and AMidi in c++. Bug: 201003646 Bug: 214447324 Test: Verified that the MIDI 1.0 path still works as before. Test: Tested that MIDI 2.0 packets are passed through the pipe with MIDIScope and MIDIKeyboard Test: atest MidiSoloTest Test: atest CtsMidiTestCases Test: NativeMidiEchoTest Change-Id: Idff8a3b9bdd05857239260fc3e6af7253f8f992f
This commit is contained in:
parent
013946d3ce
commit
a936a25198
@ -25339,6 +25339,7 @@ package android.media.midi {
|
||||
|
||||
public final class MidiDeviceInfo implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public int getDefaultProtocol();
|
||||
method public int getId();
|
||||
method public int getInputPortCount();
|
||||
method public int getOutputPortCount();
|
||||
@ -25355,6 +25356,14 @@ package android.media.midi {
|
||||
field public static final String PROPERTY_SERIAL_NUMBER = "serial_number";
|
||||
field public static final String PROPERTY_USB_DEVICE = "usb_device";
|
||||
field public static final String PROPERTY_VERSION = "version";
|
||||
field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS = 3; // 0x3
|
||||
field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_128_BITS_AND_JRTS = 4; // 0x4
|
||||
field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS = 1; // 0x1
|
||||
field public static final int PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS_AND_JRTS = 2; // 0x2
|
||||
field public static final int PROTOCOL_UMP_MIDI_2_0 = 17; // 0x11
|
||||
field public static final int PROTOCOL_UMP_MIDI_2_0_AND_JRTS = 18; // 0x12
|
||||
field public static final int PROTOCOL_UMP_USE_MIDI_CI = 0; // 0x0
|
||||
field public static final int PROTOCOL_UNKNOWN = -1; // 0xffffffff
|
||||
field public static final int TYPE_BLUETOOTH = 3; // 0x3
|
||||
field public static final int TYPE_USB = 1; // 0x1
|
||||
field public static final int TYPE_VIRTUAL = 2; // 0x2
|
||||
@ -25396,10 +25405,14 @@ package android.media.midi {
|
||||
|
||||
public final class MidiManager {
|
||||
method public android.media.midi.MidiDeviceInfo[] getDevices();
|
||||
method @NonNull public java.util.Collection<android.media.midi.MidiDeviceInfo> getDevicesForTransport(int);
|
||||
method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler);
|
||||
method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler);
|
||||
method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
|
||||
method public void registerDeviceCallbackForTransport(@NonNull android.media.midi.MidiManager.DeviceCallback, @Nullable android.os.Handler, int);
|
||||
method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
|
||||
field public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; // 0x1
|
||||
field public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; // 0x2
|
||||
}
|
||||
|
||||
public static class MidiManager.DeviceCallback {
|
||||
|
@ -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];
|
||||
|
||||
|
@ -638,13 +638,31 @@ public class MidiService extends IMidiManager.Stub {
|
||||
private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0];
|
||||
|
||||
public MidiDeviceInfo[] getDevices() {
|
||||
return getDevicesForTransport(MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public MidiDeviceInfo[] getDevicesForTransport(int transport) {
|
||||
ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
|
||||
int uid = Binder.getCallingUid();
|
||||
|
||||
synchronized (mDevicesByInfo) {
|
||||
for (Device device : mDevicesByInfo.values()) {
|
||||
if (device.isUidAllowed(uid)) {
|
||||
deviceInfos.add(device.getDeviceInfo());
|
||||
// UMP devices have protocols that are not PROTOCOL_UNKNOWN
|
||||
if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
|
||||
if (device.getDeviceInfo().getDefaultProtocol()
|
||||
!= MidiDeviceInfo.PROTOCOL_UNKNOWN) {
|
||||
deviceInfos.add(device.getDeviceInfo());
|
||||
}
|
||||
} else if (transport == MidiManager.TRANSPORT_MIDI_BYTE_STREAM) {
|
||||
if (device.getDeviceInfo().getDefaultProtocol()
|
||||
== MidiDeviceInfo.PROTOCOL_UNKNOWN) {
|
||||
deviceInfos.add(device.getDeviceInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -713,7 +731,7 @@ public class MidiService extends IMidiManager.Stub {
|
||||
@Override
|
||||
public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
|
||||
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
|
||||
Bundle properties, int type) {
|
||||
Bundle properties, int type, int defaultProtocol) {
|
||||
int uid = Binder.getCallingUid();
|
||||
if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
|
||||
throw new SecurityException("only system can create USB devices");
|
||||
@ -723,7 +741,8 @@ public class MidiService extends IMidiManager.Stub {
|
||||
|
||||
synchronized (mDevicesByInfo) {
|
||||
return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
|
||||
outputPortNames, properties, server, null, false, uid);
|
||||
outputPortNames, properties, server, null, false, uid,
|
||||
defaultProtocol);
|
||||
}
|
||||
}
|
||||
|
||||
@ -792,11 +811,12 @@ public class MidiService extends IMidiManager.Stub {
|
||||
private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
|
||||
String[] inputPortNames, String[] outputPortNames, Bundle properties,
|
||||
IMidiDeviceServer server, ServiceInfo serviceInfo,
|
||||
boolean isPrivate, int uid) {
|
||||
boolean isPrivate, int uid, int defaultProtocol) {
|
||||
|
||||
int id = mNextDeviceId++;
|
||||
MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
|
||||
inputPortNames, outputPortNames, properties, isPrivate);
|
||||
inputPortNames, outputPortNames, properties, isPrivate,
|
||||
defaultProtocol);
|
||||
|
||||
if (server != null) {
|
||||
try {
|
||||
@ -983,10 +1003,11 @@ public class MidiService extends IMidiManager.Stub {
|
||||
|
||||
synchronized (mDevicesByInfo) {
|
||||
addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL,
|
||||
numInputPorts, numOutputPorts,
|
||||
inputPortNames.toArray(EMPTY_STRING_ARRAY),
|
||||
outputPortNames.toArray(EMPTY_STRING_ARRAY),
|
||||
properties, null, serviceInfo, isPrivate, uid);
|
||||
numInputPorts, numOutputPorts,
|
||||
inputPortNames.toArray(EMPTY_STRING_ARRAY),
|
||||
outputPortNames.toArray(EMPTY_STRING_ARRAY),
|
||||
properties, null, serviceInfo, isPrivate, uid,
|
||||
MidiDeviceInfo.PROTOCOL_UNKNOWN);
|
||||
}
|
||||
// setting properties to null signals that we are no longer
|
||||
// processing a <device>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user