summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Lockwood <lockwood@google.com>2014-12-11 13:44:20 -0800
committerMike Lockwood <lockwood@google.com>2015-01-14 16:51:54 -0800
commit10024b3dc12a8552c1547b67810c77b865045cc8 (patch)
treead7f453999f8387077c40251355781e0d15cb4d1
parent34b064a1406ebb2eb10b5b1f24c845891748c238 (diff)
downloadframeworks_base-10024b3dc12a8552c1547b67810c77b865045cc8.zip
frameworks_base-10024b3dc12a8552c1547b67810c77b865045cc8.tar.gz
frameworks_base-10024b3dc12a8552c1547b67810c77b865045cc8.tar.bz2
MidiManager updates:
MIDI ports are now implemented as file descriptors directly between the sender and receiver, so the MidiService is no longer in the message path. To facilitate the above, each port has its own file descriptor, rather than multiplexing all ports on a device through a single socket. Added a new class MidiDeviceServer, which is used by implementors of MIDI devices. This replaces the MidiVirtualDevice class (which only was included in changes that were reviewed but never submitted). The USB MIDI implementation has moved from the MIDI service to the USB service. The USB MIDI implementation uses MidiDeviceServer as its interface, so we now have a common interface for all MIDI device implementations. Change-Id: I8effd1583f344beb6c940c3a24dbf20b477a6436
-rw-r--r--Android.mk1
-rw-r--r--core/java/android/midi/IMidiDeviceServer.aidl (renamed from core/java/android/midi/MidiDevice.aidl)13
-rw-r--r--core/java/android/midi/IMidiManager.aidl16
-rw-r--r--core/java/android/midi/MidiDevice.java308
-rw-r--r--core/java/android/midi/MidiDeviceInfo.java55
-rw-r--r--core/java/android/midi/MidiDeviceServer.java264
-rw-r--r--core/java/android/midi/MidiInputPort.java30
-rw-r--r--core/java/android/midi/MidiManager.java59
-rw-r--r--core/java/android/midi/MidiOutputPort.java94
-rw-r--r--core/java/android/midi/MidiPort.java86
-rw-r--r--services/core/java/com/android/server/MidiService.java306
-rw-r--r--services/core/java/com/android/server/midi/MidiDeviceBase.java170
-rw-r--r--services/core/java/com/android/server/midi/MidiService.java283
-rw-r--r--services/core/java/com/android/server/midi/UsbMidiDevice.java116
-rw-r--r--services/core/java/com/android/server/midi/VirtualMidiDevice.java91
-rw-r--r--services/core/jni/Android.mk1
-rw-r--r--services/core/jni/com_android_server_UsbMidiDevice.cpp121
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/java/com/android/server/SystemServer.java2
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaManager.java79
-rw-r--r--services/usb/java/com/android/server/usb/UsbAudioDevice.java5
-rw-r--r--services/usb/java/com/android/server/usb/UsbMidiDevice.java194
22 files changed, 1210 insertions, 1086 deletions
diff --git a/Android.mk b/Android.mk
index 76d3a13..11d4c63 100644
--- a/Android.mk
+++ b/Android.mk
@@ -174,6 +174,7 @@ LOCAL_SRC_FILES += \
core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
+ core/java/android/midi/IMidiDeviceServer.aidl \
core/java/android/midi/IMidiListener.aidl \
core/java/android/midi/IMidiManager.aidl \
core/java/android/net/IConnectivityManager.aidl \
diff --git a/core/java/android/midi/MidiDevice.aidl b/core/java/android/midi/IMidiDeviceServer.aidl
index 11bb497..31fdbbb 100644
--- a/core/java/android/midi/MidiDevice.aidl
+++ b/core/java/android/midi/IMidiDeviceServer.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2014, The Android Open Source Project
+ * Copyright (C) 2014 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
+ * 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,
@@ -16,4 +16,11 @@
package android.midi;
-parcelable MidiDevice;
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+interface IMidiDeviceServer
+{
+ ParcelFileDescriptor openInputPort(int portNumber);
+ ParcelFileDescriptor openOutputPort(int portNumber);
+}
diff --git a/core/java/android/midi/IMidiManager.aidl b/core/java/android/midi/IMidiManager.aidl
index 7a9f887..575b525 100644
--- a/core/java/android/midi/IMidiManager.aidl
+++ b/core/java/android/midi/IMidiManager.aidl
@@ -16,13 +16,11 @@
package android.midi;
-import android.hardware.usb.UsbDevice;
+import android.midi.IMidiDeviceServer;
import android.midi.IMidiListener;
-import android.midi.MidiDevice;
import android.midi.MidiDeviceInfo;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
/** @hide */
interface IMidiManager
@@ -34,14 +32,10 @@ interface IMidiManager
void unregisterListener(IBinder token, in IMidiListener listener);
// for communicating with MIDI devices
- ParcelFileDescriptor openDevice(IBinder token, in MidiDeviceInfo device);
+ IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device);
// for implementing virtual MIDI devices
- MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
- in Bundle properties);
- void unregisterVirtualDevice(IBinder token, in MidiDeviceInfo device);
-
- // for use by UsbAudioManager
- void alsaDeviceAdded(int card, int device, in UsbDevice usbDevice);
- void alsaDeviceRemoved(in UsbDevice usbDevice);
+ MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
+ int numOutputPorts, in Bundle properties, boolean isPrivate, int type);
+ void unregisterDeviceServer(in IMidiDeviceServer server);
}
diff --git a/core/java/android/midi/MidiDevice.java b/core/java/android/midi/MidiDevice.java
index 7f83896..8aaa86d 100644
--- a/core/java/android/midi/MidiDevice.java
+++ b/core/java/android/midi/MidiDevice.java
@@ -16,9 +16,8 @@
package android.midi;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.util.Log;
import java.io.FileDescriptor;
@@ -30,114 +29,31 @@ import java.util.ArrayList;
/**
* This class is used for sending and receiving data to and from an MIDI device
* Instances of this class are created by {@link MidiManager#openDevice}.
- * This class can also be used to provide the implementation for a virtual device.
- *
- * This class implements Parcelable so it can be returned from MidiService when creating
- * virtual MIDI devices.
*
* @hide
*/
-public final class MidiDevice implements Parcelable {
+public final class MidiDevice {
private static final String TAG = "MidiDevice";
private final MidiDeviceInfo mDeviceInfo;
- private ParcelFileDescriptor mParcelFileDescriptor;
- private FileInputStream mInputStream;
- private FileOutputStream mOutputStream;
-
- // lazily populated lists of ports
- private final MidiInputPort[] mInputPorts;
- private final MidiOutputPort[] mOutputPorts;
-
- // array of receiver lists, indexed by port number
- private final ArrayList<MidiReceiver>[] mReceivers;
-
- private int mReceiverCount; // total number of receivers for all ports
+ private final IMidiDeviceServer mServer;
- /**
- * Minimum size of packed message as sent through our ParcelFileDescriptor
- * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
+ /**
+ * MidiDevice should only be instantiated by MidiManager
* @hide
*/
- public static final int MIN_PACKED_MESSAGE_SIZE = 10;
+ public MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
+ mDeviceInfo = deviceInfo;
+ mServer = server;
+ }
/**
- * Maximum size of packed message as sent through our ParcelFileDescriptor
- * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
- * @hide
- */
- public static final int MAX_PACKED_MESSAGE_SIZE = 12;
-
- // This thread reads MIDI events from a socket and distributes them to the list of
- // MidiReceivers attached to this device.
- private final Thread mThread = new Thread() {
- @Override
- public void run() {
- byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE];
- ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
-
- try {
- while (true) {
- // read next event
- int count = mInputStream.read(buffer);
- if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) {
- Log.e(TAG, "Number of bytes read out of range: " + count);
- break;
- }
-
- int offset = getMessageOffset(buffer, count);
- int size = getMessageSize(buffer, count);
- long timestamp = getMessageTimeStamp(buffer, count);
- int port = getMessagePortNumber(buffer, count);
-
- synchronized (mReceivers) {
- ArrayList<MidiReceiver> receivers = mReceivers[port];
- if (receivers != null) {
- for (int i = 0; i < receivers.size(); i++) {
- MidiReceiver receiver = receivers.get(i);
- try {
- receivers.get(i).onPost(buffer, offset, size, timestamp);
- } catch (IOException e) {
- Log.e(TAG, "post failed");
- deadReceivers.add(receiver);
- }
- }
- // remove any receivers that failed
- if (deadReceivers.size() > 0) {
- for (MidiReceiver receiver: deadReceivers) {
- receivers.remove(receiver);
- mReceiverCount--;
- }
- deadReceivers.clear();
- }
- if (receivers.size() == 0) {
- mReceivers[port] = null;
- }
- // exit if we have no receivers left
- if (mReceiverCount == 0) {
- break;
- }
- }
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "read failed");
- }
- }
- };
-
- /**
- * MidiDevice should only be instantiated by MidiManager or MidiService
- * @hide
+ * Returns a {@link MidiDeviceInfo} object, which describes this device.
+ *
+ * @return the {@link MidiDeviceInfo} object
*/
- public MidiDevice(MidiDeviceInfo deviceInfo, ParcelFileDescriptor pfd) {
- mDeviceInfo = deviceInfo;
- mParcelFileDescriptor = pfd;
- int inputPorts = deviceInfo.getInputPortCount();
- int outputPorts = deviceInfo.getOutputPortCount();
- mInputPorts = new MidiInputPort[inputPorts];
- mOutputPorts = new MidiOutputPort[outputPorts];
- mReceivers = new ArrayList[outputPorts];
+ public MidiDeviceInfo getInfo() {
+ return mDeviceInfo;
}
/**
@@ -147,14 +63,15 @@ public final class MidiDevice implements Parcelable {
* @return the {@link MidiInputPort}
*/
public MidiInputPort openInputPort(int portNumber) {
- if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
- throw new IllegalArgumentException("input port number out of range");
- }
- synchronized (mInputPorts) {
- if (mInputPorts[portNumber] == null) {
- mInputPorts[portNumber] = new MidiInputPort(mOutputStream, portNumber);
+ try {
+ ParcelFileDescriptor pfd = mServer.openInputPort(portNumber);
+ if (pfd == null) {
+ return null;
}
- return mInputPorts[portNumber];
+ return new MidiInputPort(pfd, portNumber);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in openInputPort");
+ return null;
}
}
@@ -165,185 +82,20 @@ public final class MidiDevice implements Parcelable {
* @return the {@link MidiOutputPort}
*/
public MidiOutputPort openOutputPort(int portNumber) {
- if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
- throw new IllegalArgumentException("output port number out of range");
- }
- synchronized (mOutputPorts) {
- if (mOutputPorts[portNumber] == null) {
- mOutputPorts[portNumber] = new MidiOutputPort(this, portNumber);
- }
- return mOutputPorts[portNumber];
- }
- }
-
- /* package */ void connect(MidiReceiver receiver, int portNumber) {
- synchronized (mReceivers) {
- if (mReceivers[portNumber] == null) {
- mReceivers[portNumber] = new ArrayList<MidiReceiver>();
- }
- mReceivers[portNumber].add(receiver);
- if (mReceiverCount++ == 0) {
- mThread.start();
- }
- }
- }
-
- /* package */ void disconnect(MidiReceiver receiver, int portNumber) {
- synchronized (mReceivers) {
- ArrayList<MidiReceiver> receivers = mReceivers[portNumber];
- if (receivers != null && receivers.remove(receiver)) {
- mReceiverCount--;
- }
- }
- }
-
- /* package */ boolean open() {
- FileDescriptor fd = mParcelFileDescriptor.getFileDescriptor();
- try {
- mInputStream = new FileInputStream(fd);
- } catch (Exception e) {
- Log.e(TAG, "could not create mInputStream", e);
- return false;
- }
-
try {
- mOutputStream = new FileOutputStream(fd);
- } catch (Exception e) {
- Log.e(TAG, "could not create mOutputStream", e);
- return false;
- }
- return true;
- }
-
- /* package */ void close() {
- try {
- if (mInputStream != null) {
- mInputStream.close();
- }
- if (mOutputStream != null) {
- mOutputStream.close();
+ ParcelFileDescriptor pfd = mServer.openOutputPort(portNumber);
+ if (pfd == null) {
+ return null;
}
- mParcelFileDescriptor.close();
- } catch (IOException e) {
+ return new MidiOutputPort(pfd, portNumber);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in openOutputPort");
+ return null;
}
}
- /**
- * Returns a {@link MidiDeviceInfo} object, which describes this device.
- *
- * @return the {@link MidiDeviceInfo} object
- */
- public MidiDeviceInfo getInfo() {
- return mDeviceInfo;
- }
-
@Override
public String toString() {
- return ("MidiDevice: " + mDeviceInfo.toString() + " fd: " + mParcelFileDescriptor);
- }
-
- public static final Parcelable.Creator<MidiDevice> CREATOR =
- new Parcelable.Creator<MidiDevice>() {
- public MidiDevice createFromParcel(Parcel in) {
- MidiDeviceInfo deviceInfo = (MidiDeviceInfo)in.readParcelable(null);
- ParcelFileDescriptor pfd = (ParcelFileDescriptor)in.readParcelable(null);
- return new MidiDevice(deviceInfo, pfd);
- }
-
- public MidiDevice[] newArray(int size) {
- return new MidiDevice[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeParcelable(mDeviceInfo, flags);
- parcel.writeParcelable(mParcelFileDescriptor, flags);
- }
-
- /**
- * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
- *
- * message byte array contains variable length MIDI message.
- * messageSize is size of variable length MIDI message
- * timestamp is message timestamp to pack
- * dest is buffer to pack into
- * returns size of packed message
- *
- * @hide
- */
- public static int packMessage(byte[] message, int offset, int size, long timestamp,
- int portNumber, byte[] dest) {
- // pack variable length message first
- System.arraycopy(message, offset, dest, 0, size);
- int destOffset = size;
- // timestamp takes 8 bytes
- for (int i = 0; i < 8; i++) {
- dest[destOffset++] = (byte)timestamp;
- timestamp >>= 8;
- }
- // portNumber is last
- dest[destOffset++] = (byte)portNumber;
-
- return destOffset;
- }
-
- /**
- * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
- * returns the offet of of MIDI message in packed buffer
- *
- * @hide
- */
- public static int getMessageOffset(byte[] buffer, int bufferLength) {
- // message is at start of buffer
- return 0;
- }
-
- /**
- * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
- * returns size of MIDI message in packed buffer
- *
- * @hide
- */
- public static int getMessageSize(byte[] buffer, int bufferLength) {
- // message length is total buffer length minus size of the timestamp and port number
- return bufferLength - 9 /* (sizeof(timestamp) + sizeof(portNumber)) */;
+ return ("MidiDevice: " + mDeviceInfo.toString());
}
-
- /**
- * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
- * unpacks timestamp from packed buffer
- *
- * @hide
- */
- public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
- long timestamp = 0;
-
- // timestamp follows variable length message data
- int dataLength = getMessageSize(buffer, bufferLength);
- for (int i = dataLength + 7; i >= dataLength; i--) {
- // why can't Java deal with unsigned ints?
- int b = buffer[i];
- if (b < 0) b += 256;
- timestamp = (timestamp << 8) | b;
- }
- return timestamp;
- }
-
- /**
- * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
- * unpacks port number from packed buffer
- *
- * @hide
- */
- public static int getMessagePortNumber(byte[] buffer, int bufferLength) {
- // timestamp follows variable length message data and timestamp
- int dataLength = getMessageSize(buffer, bufferLength);
- return buffer[dataLength + 8 /* sizeof(timestamp) */];
- }
}
diff --git a/core/java/android/midi/MidiDeviceInfo.java b/core/java/android/midi/MidiDeviceInfo.java
index 5b57696..5cf62b5 100644
--- a/core/java/android/midi/MidiDeviceInfo.java
+++ b/core/java/android/midi/MidiDeviceInfo.java
@@ -50,10 +50,6 @@ public class MidiDeviceInfo implements Parcelable {
private final int mOutputPortCount;
private final Bundle mProperties;
- // used for USB devices only
- private final int mAlsaCard;
- private final int mAlsaDevice;
-
/**
* Bundle key for the device's manufacturer name property.
* Used with the {@link android.os.Bundle} returned by {@link #getProperties}.
@@ -83,33 +79,30 @@ public class MidiDeviceInfo implements Parcelable {
public static final String PROPERTY_USB_DEVICE = "usb_device";
/**
- * MidiDeviceInfo should only be instantiated by MidiService implementation
- * @hide
+ * Bundle key for the device's ALSA card number.
+ * Only set for USB MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
*/
- public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
- Bundle properties) {
- mType = type;
- mId = id;
- mInputPortCount = numInputPorts;
- mOutputPortCount = numOutputPorts;
- mProperties = properties;
- mAlsaCard = -1;
- mAlsaDevice = -1;
- }
+ public static final String PROPERTY_ALSA_CARD = "alsa_card";
+
+ /**
+ * Bundle key for the device's ALSA device number.
+ * Only set for USB MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ */
+ public static final String PROPERTY_ALSA_DEVICE = "alsa_device";
/**
* MidiDeviceInfo should only be instantiated by MidiService implementation
* @hide
*/
public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
- Bundle properties, int alsaCard, int alsaDevice) {
+ Bundle properties) {
mType = type;
mId = id;
mInputPortCount = numInputPorts;
mOutputPortCount = numOutputPorts;
mProperties = properties;
- mAlsaCard = alsaCard;
- mAlsaDevice = alsaDevice;
}
/**
@@ -158,20 +151,6 @@ public class MidiDeviceInfo implements Parcelable {
return mProperties;
}
- /**
- * @hide
- */
- public int getAlsaCard() {
- return mAlsaCard;
- }
-
- /**
- * @hide
- */
- public int getAlsaDevice() {
- return mAlsaDevice;
- }
-
@Override
public boolean equals(Object o) {
if (o instanceof MidiDeviceInfo) {
@@ -191,9 +170,7 @@ public class MidiDeviceInfo implements Parcelable {
return ("MidiDeviceInfo[mType=" + mType +
",mInputPortCount=" + mInputPortCount +
",mOutputPortCount=" + mOutputPortCount +
- ",mProperties=" + mProperties +
- ",mAlsaCard=" + mAlsaCard +
- ",mAlsaDevice=" + mAlsaDevice);
+ ",mProperties=" + mProperties);
}
public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -204,9 +181,7 @@ public class MidiDeviceInfo implements Parcelable {
int inputPorts = in.readInt();
int outputPorts = in.readInt();
Bundle properties = in.readBundle();
- int card = in.readInt();
- int device = in.readInt();
- return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, card, device);
+ return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties);
}
public MidiDeviceInfo[] newArray(int size) {
@@ -224,7 +199,5 @@ public class MidiDeviceInfo implements Parcelable {
parcel.writeInt(mInputPortCount);
parcel.writeInt(mOutputPortCount);
parcel.writeBundle(mProperties);
- parcel.writeInt(mAlsaCard);
- parcel.writeInt(mAlsaDevice);
}
}
diff --git a/core/java/android/midi/MidiDeviceServer.java b/core/java/android/midi/MidiDeviceServer.java
new file mode 100644
index 0000000..6ce4bf7
--- /dev/null
+++ b/core/java/android/midi/MidiDeviceServer.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2014 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 android.midi;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/** @hide */
+public final class MidiDeviceServer implements Closeable {
+ private static final String TAG = "MidiDeviceServer";
+
+ private final IMidiManager mMidiManager;
+
+ // MidiDeviceInfo for the device implemented by this server
+ private MidiDeviceInfo mDeviceInfo;
+ private int mInputPortCount;
+ private int mOutputPortCount;
+
+ // output ports for receiving messages from our clients
+ // we can have only one per port number
+ private MidiOutputPort[] mInputPortSenders;
+
+ // receivers attached to our input ports
+ private ArrayList<MidiReceiver>[] mInputPortReceivers;
+
+ // input ports for sending messages to our clients
+ // we can have multiple outputs per port number
+ private ArrayList<MidiInputPort>[] mOutputPortReceivers;
+
+ // subclass of MidiInputPort for passing to clients
+ // that notifies us when the connection has failed
+ private class ServerInputPort extends MidiInputPort {
+ ServerInputPort(ParcelFileDescriptor pfd, int portNumber) {
+ super(pfd, portNumber);
+ }
+
+ @Override
+ public void onIOException() {
+ synchronized (mOutputPortReceivers) {
+ mOutputPortReceivers[getPortNumber()] = null;
+ }
+ }
+ }
+
+ // subclass of MidiOutputPort for passing to clients
+ // that notifies us when the connection has failed
+ private class ServerOutputPort extends MidiOutputPort {
+ ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) {
+ super(pfd, portNumber);
+ }
+
+ @Override
+ public void onIOException() {
+ synchronized (mInputPortSenders) {
+ mInputPortSenders[getPortNumber()] = null;
+ }
+ }
+ }
+
+ // Binder interface stub for receiving connection requests from clients
+ private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
+
+ @Override
+ public ParcelFileDescriptor openInputPort(int portNumber) {
+ if (portNumber < 0 || portNumber >= mInputPortCount) {
+ Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
+ return null;
+ }
+
+ ParcelFileDescriptor result = null;
+ MidiOutputPort newOutputPort = null;
+
+ synchronized (mInputPortSenders) {
+ if (mInputPortSenders[portNumber] != null) {
+ Log.d(TAG, "port " + portNumber + " already open");
+ return null;
+ }
+
+ try {
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
+ OsConstants.SOCK_SEQPACKET);
+ newOutputPort = new ServerOutputPort(pair[0], portNumber);
+ mInputPortSenders[portNumber] = newOutputPort;
+ result = pair[1];
+ } catch (IOException e) {
+ Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
+ return null;
+ }
+
+ if (newOutputPort != null) {
+ ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber];
+ synchronized (receivers) {
+ for (int i = 0; i < receivers.size(); i++) {
+ newOutputPort.connect(receivers.get(i));
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public ParcelFileDescriptor openOutputPort(int portNumber) {
+ if (portNumber < 0 || portNumber >= mOutputPortCount) {
+ Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
+ return null;
+ }
+ synchronized (mOutputPortReceivers) {
+ try {
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
+ OsConstants.SOCK_SEQPACKET);
+ mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber));
+ return pair[1];
+ } catch (IOException e) {
+ Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
+ return null;
+ }
+ }
+ }
+ };
+
+ /* package */ MidiDeviceServer(IMidiManager midiManager) {
+ mMidiManager = midiManager;
+ }
+
+ /* package */ IMidiDeviceServer getBinderInterface() {
+ return mServer;
+ }
+
+ /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
+ if (mDeviceInfo != null) {
+ throw new IllegalStateException("setDeviceInfo should only be called once");
+ }
+ mDeviceInfo = deviceInfo;
+ mInputPortCount = deviceInfo.getInputPortCount();
+ mOutputPortCount = deviceInfo.getOutputPortCount();
+ mInputPortSenders = new MidiOutputPort[mInputPortCount];
+
+ mInputPortReceivers = new ArrayList[mInputPortCount];
+ for (int i = 0; i < mInputPortCount; i++) {
+ mInputPortReceivers[i] = new ArrayList<MidiReceiver>();
+ }
+
+ mOutputPortReceivers = new ArrayList[mOutputPortCount];
+ for (int i = 0; i < mOutputPortCount; i++) {
+ mOutputPortReceivers[i] = new ArrayList<MidiInputPort>();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ // FIXME - close input and output ports too?
+ mMidiManager.unregisterDeviceServer(mServer);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in unregisterDeviceServer");
+ }
+ }
+
+ /**
+ * Returns a {@link MidiDeviceInfo} object, which describes this device.
+ *
+ * @return the {@link MidiDeviceInfo} object
+ */
+ public MidiDeviceInfo getInfo() {
+ return mDeviceInfo;
+ }
+
+ /**
+ * Called to open a {@link MidiSender} to allow receiving MIDI messages
+ * on the device's input port for the specified port number.
+ *
+ * @param portNumber the number of the input port
+ * @return the {@link MidiSender}
+ */
+ public MidiSender openInputPortSender(int portNumber) {
+ if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
+ throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
+ }
+ final int portNumberF = portNumber;
+ return new MidiSender() {
+
+ @Override
+ public void connect(MidiReceiver receiver) {
+ // We always synchronize on mInputPortSenders before receivers if we need to
+ // synchronize on both.
+ synchronized (mInputPortSenders) {
+ ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
+ synchronized (receivers) {
+ receivers.add(receiver);
+ MidiOutputPort outputPort = mInputPortSenders[portNumberF];
+ if (outputPort != null) {
+ outputPort.connect(receiver);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void disconnect(MidiReceiver receiver) {
+ // We always synchronize on mInputPortSenders before receivers if we need to
+ // synchronize on both.
+ synchronized (mInputPortSenders) {
+ ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
+ synchronized (receivers) {
+ receivers.remove(receiver);
+ MidiOutputPort outputPort = mInputPortSenders[portNumberF];
+ if (outputPort != null) {
+ outputPort.disconnect(receiver);
+ }
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Called to open a {@link MidiReceiver} to allow sending MIDI messages
+ * on the virtual device's output port for the specified port number.
+ *
+ * @param portNumber the number of the output port
+ * @return the {@link MidiReceiver}
+ */
+ public MidiReceiver openOutputPortReceiver(int portNumber) {
+ if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
+ throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
+ }
+ final int portNumberF = portNumber;
+ return new MidiReceiver() {
+
+ @Override
+ public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF];
+ synchronized (receivers) {
+ for (int i = 0; i < receivers.size(); i++) {
+ // FIXME catch errors and remove dead ones
+ receivers.get(i).onPost(msg, offset, count, timestamp);
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/core/java/android/midi/MidiInputPort.java b/core/java/android/midi/MidiInputPort.java
index 31449a5..5d806cf 100644
--- a/core/java/android/midi/MidiInputPort.java
+++ b/core/java/android/midi/MidiInputPort.java
@@ -16,6 +16,10 @@
package android.midi;
+import android.os.ParcelFileDescriptor;
+
+import libcore.io.IoUtils;
+
import java.io.FileOutputStream;
import java.io.IOException;
@@ -24,15 +28,16 @@ import java.io.IOException;
*
* @hide
*/
-public final class MidiInputPort extends MidiPort implements MidiReceiver {
+public class MidiInputPort extends MidiPort implements MidiReceiver {
private final FileOutputStream mOutputStream;
+
// buffer to use for sending messages out our output stream
- private final byte[] mBuffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
+ private final byte[] mBuffer = new byte[MAX_PACKED_MESSAGE_SIZE];
- /* package */ MidiInputPort(FileOutputStream outputStream, int portNumber) {
+ /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) {
super(portNumber);
- mOutputStream = outputStream;
+ mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
}
/**
@@ -46,9 +51,20 @@ public final class MidiInputPort extends MidiPort implements MidiReceiver {
*/
public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
synchronized (mBuffer) {
- int length = MidiDevice.packMessage(msg, offset, count, timestamp, getPortNumber(),
- mBuffer);
- mOutputStream.write(mBuffer, 0, length);
+ int length = packMessage(msg, offset, count, timestamp, mBuffer);
+ try {
+ mOutputStream.write(mBuffer, 0, length);
+ } catch (IOException e) {
+ IoUtils.closeQuietly(mOutputStream);
+ // report I/O failure
+ onIOException();
+ throw e;
+ }
}
}
+
+ @Override
+ public void close() throws IOException {
+ mOutputStream.close();
+ }
}
diff --git a/core/java/android/midi/MidiManager.java b/core/java/android/midi/MidiManager.java
index 64cd4fe..2c1c7bf 100644
--- a/core/java/android/midi/MidiManager.java
+++ b/core/java/android/midi/MidiManager.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
@@ -146,40 +145,31 @@ public class MidiManager {
*/
public MidiDevice openDevice(MidiDeviceInfo deviceInfo) {
try {
- ParcelFileDescriptor pfd = mService.openDevice(mToken, deviceInfo);
- if (pfd == null) {
+ IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo);
+ if (server == null) {
Log.e(TAG, "could not open device " + deviceInfo);
return null;
}
- MidiDevice device = new MidiDevice(deviceInfo, pfd);
- if (device.open()) {
- Log.d(TAG, "openDevice returning " + device);
- return device;
- }
+ return new MidiDevice(deviceInfo, server);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openDevice");
}
return null;
}
- /**
- * Creates a new MIDI virtual device.
- * NOTE: The method for creating virtual devices is likely to change before release.
- *
- * @param numInputPorts number of input ports for the virtual device
- * @param numOutputPorts number of output ports for the virtual device
- * @param properties a {@link android.os.Bundle} containing properties describing the device
- * @return a {@link MidiDevice} object to locally represent the device
- */
- public MidiDevice createVirtualDevice(int numInputPorts, int numOutputPorts,
- Bundle properties) {
+ /** @hide */
+ public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
+ Bundle properties, boolean isPrivate, int type) {
try {
- MidiDevice device = mService.registerVirtualDevice(mToken,
- numInputPorts, numOutputPorts, properties);
- if (device != null && !device.open()) {
- device = null;
+ MidiDeviceServer server = new MidiDeviceServer(mService);
+ MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
+ numInputPorts, numOutputPorts, properties, isPrivate, type);
+ if (deviceInfo == null) {
+ Log.e(TAG, "registerVirtualDevice failed");
+ return null;
}
- return device;
+ server.setDeviceInfo(deviceInfo);
+ return server;
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in createVirtualDevice");
return null;
@@ -187,16 +177,19 @@ public class MidiManager {
}
/**
- * Removes a MIDI virtual device.
+ * Creates a new MIDI virtual device.
*
- * @param device the {@link MidiDevice} for the virtual device to remove
+ * @param numInputPorts number of input ports for the virtual device
+ * @param numOutputPorts number of output ports for the virtual device
+ * @param properties a {@link android.os.Bundle} containing properties describing the device
+ * @param isPrivate true if this device should only be visible and accessible to apps
+ * with the same UID as the caller
+ * @return a {@link MidiVirtualDevice} object to locally represent the device
*/
- public void closeVirtualDevice(MidiDevice device) {
- try {
- device.close();
- mService.unregisterVirtualDevice(mToken, device.getInfo());
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in unregisterVirtualDevice");
- }
+ public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
+ Bundle properties, boolean isPrivate) {
+ return createDeviceServer(numInputPorts, numOutputPorts, properties,
+ isPrivate, MidiDeviceInfo.TYPE_VIRTUAL);
}
+
}
diff --git a/core/java/android/midi/MidiOutputPort.java b/core/java/android/midi/MidiOutputPort.java
index 7ce286b..8b2d65d 100644
--- a/core/java/android/midi/MidiOutputPort.java
+++ b/core/java/android/midi/MidiOutputPort.java
@@ -16,20 +16,88 @@
package android.midi;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
/**
* This class is used for receiving data to a port on a MIDI device
*
* @hide
*/
-public final class MidiOutputPort extends MidiPort implements MidiSender {
+public class MidiOutputPort extends MidiPort implements MidiSender {
+ private static final String TAG = "MidiOutputPort";
+
+ private final FileInputStream mInputStream;
+
+ // array of receiver lists, indexed by port number
+ private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
+
+ private int mReceiverCount; // total number of receivers for all ports
+
+ // This thread reads MIDI events from a socket and distributes them to the list of
+ // MidiReceivers attached to this device.
+ private final Thread mThread = new Thread() {
+ @Override
+ public void run() {
+ byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE];
+ ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
- private final MidiDevice mDevice;
+ try {
+ while (true) {
+ // read next event
+ int count = mInputStream.read(buffer);
+ if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) {
+ Log.e(TAG, "Number of bytes read out of range: " + count);
+ break;
+ }
- /* package */ MidiOutputPort(MidiDevice device, int portNumber) {
+ int offset = getMessageOffset(buffer, count);
+ int size = getMessageSize(buffer, count);
+ long timestamp = getMessageTimeStamp(buffer, count);
+
+ synchronized (mReceivers) {
+ for (int i = 0; i < mReceivers.size(); i++) {
+ MidiReceiver receiver = mReceivers.get(i);
+ try {
+ receiver.onPost(buffer, offset, size, timestamp);
+ } catch (IOException e) {
+ Log.e(TAG, "post failed");
+ deadReceivers.add(receiver);
+ }
+ }
+ // remove any receivers that failed
+ if (deadReceivers.size() > 0) {
+ for (MidiReceiver receiver: deadReceivers) {
+ mReceivers.remove(receiver);
+ mReceiverCount--;
+ }
+ deadReceivers.clear();
+ }
+ // exit if we have no receivers left
+ if (mReceiverCount == 0) {
+ break;
+ }
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "read failed");
+ // report I/O failure
+ IoUtils.closeQuietly(mInputStream);
+ onIOException();
+ }
+ }
+ };
+
+
+ /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
super(portNumber);
- mDevice = device;
+ mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
}
/**
@@ -39,7 +107,12 @@ public final class MidiOutputPort extends MidiPort implements MidiSender {
* @param receiver the receiver to connect
*/
public void connect(MidiReceiver receiver) {
- mDevice.connect(receiver, getPortNumber());
+ synchronized (mReceivers) {
+ mReceivers.add(receiver);
+ if (mReceiverCount++ == 0) {
+ mThread.start();
+ }
+ }
}
/**
@@ -48,6 +121,15 @@ public final class MidiOutputPort extends MidiPort implements MidiSender {
* @param receiver the receiver to connect
*/
public void disconnect(MidiReceiver receiver) {
- mDevice.disconnect(receiver, getPortNumber());
+ synchronized (mReceivers) {
+ if (mReceivers.remove(receiver)) {
+ mReceiverCount--;
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ mInputStream.close();
}
}
diff --git a/core/java/android/midi/MidiPort.java b/core/java/android/midi/MidiPort.java
index fdd0233..53cba21 100644
--- a/core/java/android/midi/MidiPort.java
+++ b/core/java/android/midi/MidiPort.java
@@ -16,8 +16,9 @@
package android.midi;
-import java.io.FileOutputStream;
-import java.io.IOException;
+import android.util.Log;
+
+import java.io.Closeable;
/**
* This class represents a MIDI input or output port.
@@ -25,10 +26,24 @@ import java.io.IOException;
*
* @hide
*/
-public class MidiPort {
+abstract public class MidiPort implements Closeable {
+ private static final String TAG = "MidiPort";
private final int mPortNumber;
+ /**
+ * Minimum size of packed message as sent through our ParcelFileDescriptor
+ * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
+ */
+ protected static final int MIN_PACKED_MESSAGE_SIZE = 10;
+
+ /**
+ * Maximum size of packed message as sent through our ParcelFileDescriptor
+ * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
+ */
+ protected static final int MAX_PACKED_MESSAGE_SIZE = 12;
+
+
/* package */ MidiPort(int portNumber) {
mPortNumber = portNumber;
}
@@ -41,4 +56,69 @@ public class MidiPort {
public final int getPortNumber() {
return mPortNumber;
}
+
+ /**
+ * Called when an IOExeption occurs while sending or receiving data.
+ * Subclasses can override to be notified of such errors
+ *
+ */
+ public void onIOException() {
+ }
+
+ /**
+ * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
+ *
+ * message byte array contains variable length MIDI message.
+ * messageSize is size of variable length MIDI message
+ * timestamp is message timestamp to pack
+ * dest is buffer to pack into
+ * returns size of packed message
+ */
+ protected static int packMessage(byte[] message, int offset, int size, long timestamp,
+ byte[] dest) {
+ // pack variable length message first
+ System.arraycopy(message, offset, dest, 0, size);
+ int destOffset = size;
+ // timestamp takes 8 bytes
+ for (int i = 0; i < 8; i++) {
+ dest[destOffset++] = (byte)timestamp;
+ timestamp >>= 8;
+ }
+
+ return destOffset;
+ }
+
+ /**
+ * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
+ * returns the offet of of MIDI message in packed buffer
+ */
+ protected static int getMessageOffset(byte[] buffer, int bufferLength) {
+ // message is at start of buffer
+ return 0;
+ }
+
+ /**
+ * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
+ * returns size of MIDI message in packed buffer
+ */
+ protected static int getMessageSize(byte[] buffer, int bufferLength) {
+ // message length is total buffer length minus size of the timestamp and port number
+ return bufferLength - 8 /* sizeof(timestamp) */;
+ }
+
+ /**
+ * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
+ * unpacks timestamp from packed buffer
+ */
+ protected static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
+ long timestamp = 0;
+
+ // timestamp follows variable length message data
+ int dataLength = getMessageSize(buffer, bufferLength);
+ for (int i = dataLength + 7; i >= dataLength; i--) {
+ int b = (int)buffer[i] & 0xFF;
+ timestamp = (timestamp << 8) | b;
+ }
+ return timestamp;
+ }
}
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
new file mode 100644
index 0000000..38f1cb8
--- /dev/null
+++ b/services/core/java/com/android/server/MidiService.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2014 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 an
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.midi.IMidiDeviceServer;
+import android.midi.IMidiListener;
+import android.midi.IMidiManager;
+import android.midi.MidiDeviceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class MidiService extends IMidiManager.Stub {
+ private static final String TAG = "MidiService";
+
+ private final Context mContext;
+
+ // list of all our clients, keyed by Binder token
+ private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
+
+ // list of all devices, keyed by MidiDeviceInfo
+ private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo
+ = new HashMap<MidiDeviceInfo, Device>();
+
+ // list of all devices, keyed by IMidiDeviceServer
+ private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>();
+
+ // used for assigning IDs to MIDI devices
+ private int mNextDeviceId = 1;
+
+ private final class Client implements IBinder.DeathRecipient {
+ // Binder token for this client
+ private final IBinder mToken;
+ // This client's UID
+ private final int mUid;
+ // This client's PID
+ private final int mPid;
+ // List of all receivers for this client
+ private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
+
+ public Client(IBinder token) {
+ mToken = token;
+ mUid = Binder.getCallingUid();
+ mPid = Binder.getCallingPid();
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public void addListener(IMidiListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeListener(IMidiListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ removeClient(mToken);
+ }
+ }
+
+ public void deviceAdded(Device device) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ MidiDeviceInfo deviceInfo = device.getDeviceInfo();
+ try {
+ for (IMidiListener listener : mListeners) {
+ listener.onDeviceAdded(deviceInfo);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
+ public void deviceRemoved(Device device) {
+ // ignore private devices that our client cannot access
+ if (!device.isUidAllowed(mUid)) return;
+
+ MidiDeviceInfo deviceInfo = device.getDeviceInfo();
+ try {
+ for (IMidiListener listener : mListeners) {
+ listener.onDeviceRemoved(deviceInfo);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception", e);
+ }
+ }
+
+ public void binderDied() {
+ removeClient(mToken);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Client: UID: ");
+ sb.append(mUid);
+ sb.append(" PID: ");
+ sb.append(mPid);
+ sb.append(" listener count: ");
+ sb.append(mListeners.size());
+ return sb.toString();
+ }
+ }
+
+ private Client getClient(IBinder token) {
+ synchronized (mClients) {
+ Client client = mClients.get(token);
+ if (client == null) {
+ client = new Client(token);
+
+ try {
+ token.linkToDeath(client, 0);
+ } catch (RemoteException e) {
+ return null;
+ }
+ mClients.put(token, client);
+ }
+ return client;
+ }
+ }
+
+ private void removeClient(IBinder token) {
+ mClients.remove(token);
+ }
+
+ private final class Device implements IBinder.DeathRecipient {
+ private final IMidiDeviceServer mServer;
+ private final MidiDeviceInfo mDeviceInfo;
+ // UID of device creator
+ private final int mUid;
+ // PID of device creator
+ private final int mPid;
+
+ public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo) {
+ mServer = server;
+ mDeviceInfo = deviceInfo;
+ mUid = Binder.getCallingUid();
+ mPid = Binder.getCallingPid();
+ }
+
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
+
+ public IMidiDeviceServer getDeviceServer() {
+ return mServer;
+ }
+
+ public boolean isUidAllowed(int uid) {
+ // FIXME
+ return true;
+ }
+
+ public void binderDied() {
+ synchronized (mDevicesByServer) {
+ removeDeviceLocked(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Device: ");
+ sb.append(mDeviceInfo);
+ sb.append(" UID: ");
+ sb.append(mUid);
+ sb.append(" PID: ");
+ sb.append(mPid);
+ return sb.toString();
+ }
+ }
+
+ public MidiService(Context context) {
+ mContext = context;
+ }
+
+ public void registerListener(IBinder token, IMidiListener listener) {
+ Client client = getClient(token);
+ if (client == null) return;
+ client.addListener(listener);
+ }
+
+ public void unregisterListener(IBinder token, IMidiListener listener) {
+ Client client = getClient(token);
+ if (client == null) return;
+ client.removeListener(listener);
+ }
+
+ public MidiDeviceInfo[] getDeviceList() {
+ return mDevicesByInfo.keySet().toArray(new MidiDeviceInfo[0]);
+ }
+
+ public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
+ Device device = mDevicesByInfo.get(deviceInfo);
+ if (device == null) {
+ Log.e(TAG, "device not found in openDevice: " + deviceInfo);
+ return null;
+ }
+
+ if (!device.isUidAllowed(Binder.getCallingUid())) {
+ throw new SecurityException("Attempt to open private device with wrong UID");
+ }
+
+ return device.getDeviceServer();
+ }
+
+ public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
+ int numOutputPorts, Bundle properties, boolean isPrivate, int type) {
+ if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("only system can create non-virtual devices");
+ }
+
+ MidiDeviceInfo deviceInfo;
+ Device device;
+
+ synchronized (mDevicesByServer) {
+ int id = mNextDeviceId++;
+ deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, properties);
+ IBinder binder = server.asBinder();
+ device = new Device(server, deviceInfo);
+ try {
+ binder.linkToDeath(device, 0);
+ } catch (RemoteException e) {
+ return null;
+ }
+ mDevicesByInfo.put(deviceInfo, device);
+ mDevicesByServer.put(server.asBinder(), device);
+ }
+
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceAdded(device);
+ }
+ }
+
+ return deviceInfo;
+ }
+
+ public void unregisterDeviceServer(IMidiDeviceServer server) {
+ synchronized (mDevicesByServer) {
+ removeDeviceLocked(mDevicesByServer.get(server.asBinder()));
+ }
+ }
+
+ // synchronize on mDevicesByServer
+ private void removeDeviceLocked(Device device) {
+ if (mDevicesByServer.remove(device.getDeviceServer().asBinder()) != null) {
+ mDevicesByInfo.remove(device.getDeviceInfo());
+
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceRemoved(device);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+
+ pw.println("MIDI Manager State:");
+ pw.increaseIndent();
+
+ pw.println("Devices:");
+ pw.increaseIndent();
+ for (Device device : mDevicesByInfo.values()) {
+ pw.println(device.toString());
+ }
+ pw.decreaseIndent();
+
+ pw.println("Clients:");
+ pw.increaseIndent();
+ for (Client client : mClients.values()) {
+ pw.println(client.toString());
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/midi/MidiDeviceBase.java b/services/core/java/com/android/server/midi/MidiDeviceBase.java
deleted file mode 100644
index 4289584..0000000
--- a/services/core/java/com/android/server/midi/MidiDeviceBase.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2014 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 an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.Binder;
-import android.os.ParcelFileDescriptor;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Abstract internal base class for entities in MidiService.
- * This class contains two threads for reading and writing MIDI events.
- * On one end we have the readMessage() and writeMessage() methods, which must be
- * implemented by a subclass. On the other end we have file descriptors for sockets
- * attached to client applications.
- */
-abstract class MidiDeviceBase {
- private static final String TAG = "MidiDeviceBase";
-
- final MidiDeviceInfo mDeviceInfo;
- private ReaderThread mReaderThread;
- private WriterThread mWriterThread;
- private ParcelFileDescriptor mParcelFileDescriptor;
-
- // Reads MIDI messages from readMessage() and write them to one or more clients.
- private class ReaderThread extends Thread {
- private final ArrayList<FileOutputStream> mOutputStreams =
- new ArrayList<FileOutputStream>();
-
- @Override
- public void run() {
- byte[] buffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
-
- while (true) {
- try {
- int count = readMessage(buffer);
-
- if (count > 0) {
- synchronized (mOutputStreams) {
- for (int i = 0; i < mOutputStreams.size(); i++) {
- FileOutputStream fos = mOutputStreams.get(i);
- try {
- fos.write(buffer, 0, count);
- } catch (IOException e) {
- Log.e(TAG, "write failed", e);
- mOutputStreams.remove(fos);
- }
- }
- }
- }
- } catch (IOException e) {
- Log.e(TAG, "read failed", e);
- break;
- }
- }
- }
-
- public void addListener(FileOutputStream fos) {
- synchronized (mOutputStreams) {
- mOutputStreams.add(fos);
- }
- }
-
- public void removeListener(FileOutputStream fos) {
- synchronized (mOutputStreams) {
- mOutputStreams.remove(fos);
- }
- }
- }
-
- // Reads MIDI messages from our client and writes them by calling writeMessage()
- private class WriterThread extends Thread {
- private final FileInputStream mInputStream;
-
- public WriterThread(FileInputStream fis) {
- mInputStream = fis;
- }
-
- @Override
- public void run() {
- byte[] buffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
-
- while (true) {
- try {
- int count = mInputStream.read(buffer);
- writeMessage(buffer, count);
- } catch (IOException e) {
- Log.e(TAG, "WriterThread failed", e);
- break;
- }
- }
- }
- }
-
- public MidiDeviceBase(MidiDeviceInfo info) {
- mDeviceInfo = info;
- }
-
- public MidiDeviceInfo getInfo() {
- return mDeviceInfo;
- }
-
- public ParcelFileDescriptor getFileDescriptor() {
- synchronized (this) {
- if (mReaderThread == null) {
- if (!open()) {
- return null;
- }
- mReaderThread = new ReaderThread();
- mReaderThread.start();
- }
- }
-
- try {
- ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
- OsConstants.SOCK_SEQPACKET);
- mParcelFileDescriptor = pair[0];
- FileOutputStream fos = new FileOutputStream(mParcelFileDescriptor.getFileDescriptor());
- mReaderThread.addListener(fos);
-
- // return an error if the device is already open for writing?
- if (mWriterThread == null) {
- FileInputStream fis = new FileInputStream(
- mParcelFileDescriptor.getFileDescriptor());
- mWriterThread = new WriterThread(fis);
- mWriterThread.start();
- }
-
- return pair[1];
- } catch (IOException e) {
- Log.e(TAG, "could not create ParcelFileDescriptor pair", e);
- return null;
- }
- }
-
- abstract boolean open();
-
- void close() {
- try {
- if (mParcelFileDescriptor != null) {
- mParcelFileDescriptor.close();
- }
- } catch (IOException e) {
- }
- }
-
- abstract int readMessage(byte[] buffer) throws IOException;
- abstract void writeMessage(byte[] buffer, int count) throws IOException;
-}
diff --git a/services/core/java/com/android/server/midi/MidiService.java b/services/core/java/com/android/server/midi/MidiService.java
deleted file mode 100644
index 29a8339..0000000
--- a/services/core/java/com/android/server/midi/MidiService.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2014 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 an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.content.Context;
-import android.hardware.usb.UsbDevice;
-import android.midi.IMidiListener;
-import android.midi.IMidiManager;
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class MidiService extends IMidiManager.Stub {
- private static final String TAG = "MidiService";
-
- private final Context mContext;
-
- // list of all our clients, keyed by Binder token
- private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
-
- // list of all devices, keyed by ID
- private final HashMap<Integer, MidiDeviceBase> mDevices
- = new HashMap<Integer, MidiDeviceBase>();
-
- // list of all USB devices, keyed by USB device.
- private final HashMap<UsbDevice, UsbMidiDevice> mUsbDevices
- = new HashMap<UsbDevice, UsbMidiDevice>();
-
- // used for assigning IDs to MIDI devices
- private int mNextDeviceId = 1;
-
- private final class Client implements IBinder.DeathRecipient {
- private final IBinder mToken;
- private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
- private final ArrayList<MidiDeviceBase> mVirtualDevices = new ArrayList<MidiDeviceBase>();
-
- public Client(IBinder token) {
- mToken = token;
- }
-
- public void close() {
- for (MidiDeviceBase device : mVirtualDevices) {
- device.close();
- }
- mVirtualDevices.clear();
- }
-
- public void addListener(IMidiListener listener) {
- mListeners.add(listener);
- }
-
- public void removeListener(IMidiListener listener) {
- mListeners.remove(listener);
- if (mListeners.size() == 0 && mVirtualDevices.size() == 0) {
- removeClient(mToken);
- }
- }
-
- public void addVirtualDevice(MidiDeviceBase device) {
- mVirtualDevices.add(device);
- }
-
- public void removeVirtualDevice(MidiDeviceBase device) {
- mVirtualDevices.remove(device);
- }
-
- public void deviceAdded(MidiDeviceInfo device) {
- try {
- for (IMidiListener listener : mListeners) {
- listener.onDeviceAdded(device);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "remote exception", e);
- }
- }
-
- public void deviceRemoved(MidiDeviceInfo device) {
- try {
- for (IMidiListener listener : mListeners) {
- listener.onDeviceRemoved(device);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "remote exception", e);
- }
- }
-
- public void binderDied() {
- removeClient(mToken);
- }
- }
-
-
- private Client getClient(IBinder token) {
- synchronized (mClients) {
- Client client = mClients.get(token);
- if (client == null) {
- client = new Client(token);
-
- try {
- token.linkToDeath(client, 0);
- } catch (RemoteException e) {
- return null;
- }
- mClients.put(token, client);
- }
- return client;
- }
- }
-
- private void removeClient(IBinder token) {
- synchronized (mClients) {
- Client client = mClients.remove(token);
- if (client != null) {
- client.close();
- }
- }
- }
-
- public MidiService(Context context) {
- mContext = context;
- }
-
- public void registerListener(IBinder token, IMidiListener listener) {
- Client client = getClient(token);
- if (client == null) return;
- client.addListener(listener);
- }
-
- public void unregisterListener(IBinder token, IMidiListener listener) {
- Client client = getClient(token);
- if (client == null) return;
- client.removeListener(listener);
- }
-
- public MidiDeviceInfo[] getDeviceList() {
- ArrayList<MidiDeviceInfo> infos = new ArrayList<MidiDeviceInfo>();
- for (MidiDeviceBase device : mDevices.values()) {
- infos.add(device.getInfo());
- }
- return infos.toArray(new MidiDeviceInfo[0]);
- }
-
- public ParcelFileDescriptor openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
- MidiDeviceBase device = mDevices.get(deviceInfo.getId());
- if (device == null) {
- Log.e(TAG, "device not found in openDevice: " + deviceInfo);
- return null;
- }
-
- return device.getFileDescriptor();
- }
-
- public MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
- Bundle properties) {
- VirtualMidiDevice device;
- Client client = getClient(token);
- if (client == null) return null;
-
- synchronized (mDevices) {
- int id = mNextDeviceId++;
- MidiDeviceInfo deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_VIRTUAL, id,
- numInputPorts, numOutputPorts, properties);
-
- device = new VirtualMidiDevice(deviceInfo);
- if (!device.open()) {
- return null;
- }
- mDevices.put(id, device);
- client.addVirtualDevice(device);
- }
-
- synchronized (mClients) {
- MidiDeviceInfo deviceInfo = device.getInfo();
- for (Client c : mClients.values()) {
- c.deviceAdded(deviceInfo);
- }
- }
-
- return device.getProxy();
- }
-
- public void unregisterVirtualDevice(IBinder token, MidiDeviceInfo deviceInfo) {
- Client client = getClient(token);
- if (client == null) return;
-
- MidiDeviceBase device;
- synchronized (mDevices) {
- device = mDevices.remove(deviceInfo.getId());
- }
-
- if (device != null) {
- client.removeVirtualDevice(device);
- device.close();
-
- synchronized (mClients) {
- for (Client c : mClients.values()) {
- c.deviceRemoved(deviceInfo);
- }
- }
- }
- }
-
- // called by UsbAudioManager to notify of new USB MIDI devices
- public void alsaDeviceAdded(int card, int device, UsbDevice usbDevice) {
- Log.d(TAG, "alsaDeviceAdded: card:" + card + " device:" + device);
-
- MidiDeviceInfo deviceInfo;
-
- synchronized (mDevices) {
- int id = mNextDeviceId++;
- Bundle properties = new Bundle();
- properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER,
- usbDevice.getManufacturerName());
- properties.putString(MidiDeviceInfo.PROPERTY_MODEL,
- usbDevice.getProductName());
- properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
- usbDevice.getSerialNumber());
- properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
-
- // FIXME - multiple ports not supported yet
- int inputPorts = 1;
- int outputPorts = 1;
-
- deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_USB, id, inputPorts, outputPorts,
- properties, card, device);
- UsbMidiDevice midiDevice = new UsbMidiDevice(deviceInfo);
- mDevices.put(id, midiDevice);
- mUsbDevices.put(usbDevice, midiDevice);
- }
-
- synchronized (mClients) {
- for (Client client : mClients.values()) {
- client.deviceAdded(deviceInfo);
- }
- }
- }
-
- // called by UsbAudioManager to notify of removed USB MIDI devices
- public void alsaDeviceRemoved(UsbDevice usbDevice) {
- MidiDeviceInfo deviceInfo = null;
-
- synchronized (mDevices) {
- MidiDeviceBase device = mUsbDevices.remove(usbDevice);
- if (device != null) {
- device.close();
- deviceInfo = device.getInfo();
- mDevices.remove(deviceInfo.getId());
- }
- }
-
- Log.d(TAG, "alsaDeviceRemoved: " + deviceInfo);
-
- if (deviceInfo != null) {
- synchronized (mClients) {
- for (Client client : mClients.values()) {
- client.deviceRemoved(deviceInfo);
- }
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/midi/UsbMidiDevice.java b/services/core/java/com/android/server/midi/UsbMidiDevice.java
deleted file mode 100644
index 3d42c67..0000000
--- a/services/core/java/com/android/server/midi/UsbMidiDevice.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2014 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 an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.midi.MidiUtils;
-import android.os.Binder;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-// This is our subclass of MidiDeviceBase for communicating with USB MIDI devices
-// via the ALSA driver file system.
-class UsbMidiDevice extends MidiDeviceBase {
- private static final String TAG = "UsbMidiDevice";
-
- private FileInputStream mInputStream;
- private FileOutputStream mOutputStream;
- private final byte[] mBuffer = new byte[3];
-
- public UsbMidiDevice(MidiDeviceInfo info) {
- super(info);
- }
-
- public boolean open() {
- if (mInputStream != null && mOutputStream != null) {
- // already open
- return true;
- }
-
- int card = mDeviceInfo.getAlsaCard();
- int device = mDeviceInfo.getAlsaDevice();
- if (card == -1 || device == -1) {
- Log.e(TAG, "Not a USB device!");
- return false;
- }
-
- // clear calling identity so we can access the driver file.
- long identity = Binder.clearCallingIdentity();
-
- File file = new File("/dev/snd/midiC" + card + "D" + device);
- try {
- mInputStream = new FileInputStream(file);
- mOutputStream = new FileOutputStream(file);
- } catch (Exception e) {
- Log.e(TAG, "could not open " + file);
- return false;
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
-
- return true;
- }
-
- void close() {
- super.close();
- try {
- if (mInputStream != null) {
- mInputStream.close();
- }
- if (mOutputStream != null) {
- mOutputStream.close();
- }
- } catch (IOException e) {
- }
- }
-
- // Reads a message from the ALSA driver.
- // The driver may return multiple messages, so we have to read byte at a time.
- int readMessage(byte[] buffer) throws IOException {
- if (mInputStream.read(mBuffer, 0, 1) != 1) {
- Log.e(TAG, "could not read command byte");
- return -1;
- }
- int dataSize = MidiUtils.getMessageDataSize(mBuffer[0]);
- if (dataSize < 0) {
- return -1;
- }
- if (dataSize > 0) {
- if (mInputStream.read(mBuffer, 1, dataSize) != dataSize) {
- Log.e(TAG, "could not read command data");
- return -1;
- }
- }
- return MidiDevice.packMessage(mBuffer, 0, dataSize + 1, System.nanoTime(),
- 0, // FIXME - multiple ports not supported yet
- buffer);
- }
-
- // writes a message to the ALSA driver
- void writeMessage(byte[] buffer, int count) throws IOException {
- int offset = MidiDevice.getMessageOffset(buffer, count);
- int size = MidiDevice.getMessageSize(buffer, count);
- // FIXME - multiple ports not supported yet
- mOutputStream.write(buffer, offset, count);
- }
-}
-
diff --git a/services/core/java/com/android/server/midi/VirtualMidiDevice.java b/services/core/java/com/android/server/midi/VirtualMidiDevice.java
deleted file mode 100644
index 5b39045..0000000
--- a/services/core/java/com/android/server/midi/VirtualMidiDevice.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2014 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 an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.ParcelFileDescriptor;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-// Our subclass of MidiDeviceBase to implement a virtual MIDI device
-class VirtualMidiDevice extends MidiDeviceBase {
- private static final String TAG = "VirtualMidiDevice";
-
- private ParcelFileDescriptor[] mFileDescriptors;
- private FileInputStream mInputStream;
- private FileOutputStream mOutputStream;
-
- public VirtualMidiDevice(MidiDeviceInfo info) {
- super(info);
- }
-
- public boolean open() {
- if (mInputStream != null && mOutputStream != null) {
- // already open
- return true;
- }
-
- try {
- mFileDescriptors = ParcelFileDescriptor.createSocketPair(
- OsConstants.SOCK_SEQPACKET);
- FileDescriptor fd = mFileDescriptors[0].getFileDescriptor();
- mInputStream = new FileInputStream(fd);
- mOutputStream = new FileOutputStream(fd);
- return true;
- } catch (IOException e) {
- Log.e(TAG, "failed to create ParcelFileDescriptor pair");
- return false;
- }
- }
-
- void close() {
- super.close();
- try {
- if (mInputStream != null) {
- mInputStream.close();
- }
- if (mOutputStream != null) {
- mOutputStream.close();
- }
- if (mFileDescriptors != null && mFileDescriptors[0] != null) {
- mFileDescriptors[0].close();
- // file descriptor 1 is passed to client process
- }
- } catch (IOException e) {
- }
- }
-
- MidiDevice getProxy() {
- return new MidiDevice(mDeviceInfo, mFileDescriptors[1]);
- }
-
- int readMessage(byte[] buffer) throws IOException {
- int ret = mInputStream.read(buffer);
- // for now, throw away the timestamp
- return ret - 8;
- }
-
- void writeMessage(byte[] buffer, int count) throws IOException {
- mOutputStream.write(buffer, 0, count);
- }
-}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index dc073ad..7b74e91 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -22,6 +22,7 @@ LOCAL_SRC_FILES += \
$(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
$(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
$(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
$(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
$(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp
new file mode 100644
index 0000000..94853b8
--- /dev/null
+++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "UsbMidiDeviceJNI"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <asm/byteorder.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sound/asound.h>
+
+namespace android
+{
+
+static jclass sFileDescriptorClass;
+
+static jint
+android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */,
+ jint card, jint device)
+{
+ char path[100];
+
+ snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
+ int fd = open(path, O_RDWR);
+ if (fd < 0) {
+ ALOGE("could not open %s", path);
+ return 0;
+ }
+
+ struct snd_rawmidi_info info;
+ memset(&info, 0, sizeof(info));
+ info.device = device;
+ int ret = ioctl(fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, &info);
+ close(fd);
+
+ if (ret < 0) {
+ ALOGE("SNDRV_CTL_IOCTL_RAWMIDI_INFO failed, errno: %d path: %s", errno, path);
+ return -1;
+ }
+
+ ALOGD("subdevices_count: %d", info.subdevices_count);
+ return info.subdevices_count;
+}
+
+static jobjectArray
+android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device,
+ jint subdevice_count)
+{
+ char path[100];
+
+ snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
+
+ jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL);
+ if (!fds) {
+ return NULL;
+ }
+
+ // to support multiple subdevices we open the same file multiple times
+ for (int i = 0; i < subdevice_count; i++) {
+ int fd = open(path, O_RDWR);
+ if (fd < 0) {
+ ALOGE("open failed on %s for index %d", path, i);
+ return NULL;
+ }
+
+ jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
+ env->SetObjectArrayElement(fds, i, fileDescriptor);
+ env->DeleteLocalRef(fileDescriptor);
+ }
+
+ return fds;
+}
+
+static JNINativeMethod method_table[] = {
+ { "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count },
+ { "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open },
+};
+
+int register_android_server_UsbMidiDevice(JNIEnv *env)
+{
+ jclass clazz = env->FindClass("java/io/FileDescriptor");
+ if (clazz == NULL) {
+ ALOGE("Can't find java/io/FileDescriptor");
+ return -1;
+ }
+ sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);;
+
+ clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
+ if (clazz == NULL) {
+ ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
+ return -1;
+ }
+
+ return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
+ method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c65b3be..7db7414 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -32,6 +32,7 @@ int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
+int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
@@ -65,6 +66,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
register_android_server_UsbDeviceManager(env);
+ register_android_server_UsbMidiDevice(env);
register_android_server_UsbHostManager(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index eca398c..7f9af31 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -74,7 +74,7 @@ import com.android.server.lights.LightsService;
import com.android.server.media.MediaRouterService;
import com.android.server.media.MediaSessionService;
import com.android.server.media.projection.MediaProjectionManagerService;
-import com.android.server.midi.MidiService;
+import com.android.server.MidiService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.notification.NotificationManagerService;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 32ca723..6589135 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -24,15 +24,15 @@ import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbInterface;
import android.media.AudioManager;
-import android.midi.IMidiManager;
import android.os.FileObserver;
import android.os.IBinder;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
+import libcore.io.IoUtils;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -42,14 +42,13 @@ import java.util.ArrayList;
/**
* UsbAlsaManager manages USB audio and MIDI devices.
*/
-public class UsbAlsaManager {
+public final class UsbAlsaManager {
private static final String TAG = UsbAlsaManager.class.getSimpleName();
private static final boolean DEBUG = true;
private static final String ALSA_DIRECTORY = "/dev/snd/";
private final Context mContext;
- private IMidiManager mMidiManager;
private final AlsaCardsParser mCardsParser = new AlsaCardsParser();
private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser();
@@ -60,6 +59,9 @@ public class UsbAlsaManager {
private final HashMap<UsbDevice,UsbAudioDevice>
mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>();
+ private final HashMap<UsbDevice,UsbMidiDevice>
+ mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>();
+
private final HashMap<String,AlsaDevice>
mAlsaDevices = new HashMap<String,AlsaDevice>();
@@ -121,8 +123,6 @@ public class UsbAlsaManager {
}
public void systemReady() {
- final IBinder b = ServiceManager.getService(Context.MIDI_SERVICE);
- mMidiManager = IMidiManager.Stub.asInterface(b);
mAlsaObserver.startWatching();
// add existing alsa devices
@@ -151,7 +151,6 @@ public class UsbAlsaManager {
intent.putExtra("device", audioDevice.mDevice);
intent.putExtra("hasPlayback", audioDevice.mHasPlayback);
intent.putExtra("hasCapture", audioDevice.mHasCapture);
- intent.putExtra("hasMIDI", audioDevice.mHasMIDI);
intent.putExtra("class", audioDevice.mDeviceClass);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -235,9 +234,9 @@ public class UsbAlsaManager {
/*
* Select the default device of the specified card.
*/
- /* package */ boolean selectCard(int card) {
+ /* package */ boolean selectAudioCard(int card) {
if (DEBUG) {
- Slog.d(TAG, "selectCard() card:" + card);
+ Slog.d(TAG, "selectAudioCard() card:" + card);
}
if (!mCardsParser.isCardUsb(card)) {
// Don't. AudioPolicyManager has logic for falling back to internal devices.
@@ -259,7 +258,6 @@ public class UsbAlsaManager {
boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
boolean hasCapture = mDevicesParser.hasCaptureDevices(card);
- boolean hasMidi = mDevicesParser.hasMIDIDevices(card);
int deviceClass =
(mCardsParser.isCardUsb(card)
? UsbAudioDevice.kAudioDeviceClass_External
@@ -275,21 +273,13 @@ public class UsbAlsaManager {
if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) {
return false;
}
- //TODO - seems to me that we need to decouple the above tests for audio
- // from the one below for MIDI.
-
- // MIDI device file needed/present?
- AlsaDevice midiDevice = null;
- if (hasMidi) {
- midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI);
- }
if (DEBUG) {
Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture);
}
mSelectedAudioDevice =
- new UsbAudioDevice(card, device, hasPlayback, hasCapture, hasMidi, deviceClass);
+ new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass);
mSelectedAudioDevice.mDeviceName = mCardsParser.getCardRecordFor(card).mCardName;
mSelectedAudioDevice.mDeviceDescription =
mCardsParser.getCardRecordFor(card).mCardDescription;
@@ -304,7 +294,7 @@ public class UsbAlsaManager {
Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()");
}
mCardsParser.scan();
- return selectCard(mCardsParser.getDefaultCard());
+ return selectAudioCard(mCardsParser.getDefaultCard());
}
/* package */ void deviceAdded(UsbDevice usbDevice) {
@@ -314,7 +304,6 @@ public class UsbAlsaManager {
// Is there an audio interface in there?
boolean isAudioDevice = false;
- AlsaDevice midiDevice = null;
// FIXME - handle multiple configurations?
int interfaceCount = usbDevice.getInterfaceCount();
@@ -347,36 +336,44 @@ public class UsbAlsaManager {
// If the default isn't a USB device, let the existing "select internal mechanism"
// handle the selection.
if (mCardsParser.isCardUsb(addedCard)) {
- selectCard(addedCard);
+ selectAudioCard(addedCard);
mAudioDevices.put(usbDevice, mSelectedAudioDevice);
- }
- if (midiDevice != null && mMidiManager != null) {
- try {
- mMidiManager.alsaDeviceAdded(midiDevice.mCard, midiDevice.mDevice, usbDevice);
- } catch (RemoteException e) {
- Slog.e(TAG, "MIDI Manager dead", e);
+ // look for MIDI devices
+
+ // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above.
+ // Uncomment this next line if that behavior changes in the fugure.
+ // mDevicesParser.scan()
+
+ boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard);
+ if (hasMidi) {
+ int device = mDevicesParser.getDefaultDeviceNum(addedCard);
+ AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI);
+ if (alsaDevice != null) {
+ UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, usbDevice,
+ alsaDevice.mCard, alsaDevice.mDevice);
+ if (usbMidiDevice != null) {
+ mMidiDevices.put(usbDevice, usbMidiDevice);
+ }
+ }
}
}
}
- /* package */ void deviceRemoved(UsbDevice device) {
+ /* package */ void deviceRemoved(UsbDevice usbDevice) {
if (DEBUG) {
- Slog.d(TAG, "deviceRemoved(): " + device);
+ Slog.d(TAG, "deviceRemoved(): " + usbDevice);
}
- UsbAudioDevice audioDevice = mAudioDevices.remove(device);
+ UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice);
if (audioDevice != null) {
if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
sendDeviceNotification(audioDevice, false);
}
- if (audioDevice.mHasMIDI) {
- try {
- mMidiManager.alsaDeviceRemoved(device);
- } catch (RemoteException e) {
- Slog.e(TAG, "MIDI Manager dead", e);
- }
- }
+ }
+ UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice);
+ if (usbMidiDevice != null) {
+ IoUtils.closeQuietly(usbMidiDevice);
}
mSelectedAudioDevice = null;
@@ -400,10 +397,14 @@ public class UsbAlsaManager {
// Logging
//
public void dump(FileDescriptor fd, PrintWriter pw) {
- pw.println(" USB AudioDevices:");
+ pw.println(" USB Audio Devices:");
for (UsbDevice device : mAudioDevices.keySet()) {
pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device));
}
+ pw.println(" USB MIDI Devices:");
+ for (UsbDevice device : mMidiDevices.keySet()) {
+ pw.println(" " + device.getDeviceName() + ": " + mMidiDevices.get(device));
+ }
}
public void logDevicesList(String title) {
diff --git a/services/usb/java/com/android/server/usb/UsbAudioDevice.java b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
index b7b9563..069d917 100644
--- a/services/usb/java/com/android/server/usb/UsbAudioDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAudioDevice.java
@@ -24,7 +24,6 @@ public final class UsbAudioDevice {
public int mDevice;
public boolean mHasPlayback;
public boolean mHasCapture;
- public boolean mHasMIDI;
// Device "class" flags
public static final int kAudioDeviceClassMask = 0x00FFFFFF;
@@ -41,12 +40,11 @@ public final class UsbAudioDevice {
public String mDeviceDescription = "";
public UsbAudioDevice(int card, int device,
- boolean hasPlayback, boolean hasCapture, boolean hasMidi, int deviceClass) {
+ boolean hasPlayback, boolean hasCapture, int deviceClass) {
mCard = card;
mDevice = device;
mHasPlayback = hasPlayback;
mHasCapture = hasCapture;
- mHasMIDI = hasMidi;
mDeviceClass = deviceClass;
}
@@ -58,7 +56,6 @@ public final class UsbAudioDevice {
sb.append(", description: " + mDeviceDescription);
sb.append(", hasPlayback: " + mHasPlayback);
sb.append(", hasCapture: " + mHasCapture);
- sb.append(", hasMidi: " + mHasMIDI);
sb.append(", class: 0x" + Integer.toHexString(mDeviceClass) + "]");
return sb.toString();
}
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
new file mode 100644
index 0000000..aaac331
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2014 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.midi.MidiDeviceInfo;
+import android.midi.MidiDeviceServer;
+import android.midi.MidiManager;
+import android.midi.MidiReceiver;
+import android.midi.MidiSender;
+import android.midi.MidiUtils;
+import android.os.Bundle;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructPollfd;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public final class UsbMidiDevice implements Closeable {
+ private static final String TAG = "UsbMidiDevice";
+
+ private final MidiDeviceServer mServer;
+ private final MidiReceiver[] mOutputPortReceivers;
+
+ // for polling multiple FileDescriptors for MIDI events
+ private final StructPollfd[] mPollFDs;
+ private final FileInputStream[] mInputStreams;
+ private final FileOutputStream[] mOutputStreams;
+
+ public static UsbMidiDevice create(Context context, UsbDevice usbDevice, int card, int device) {
+ MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+ if (midiManager == null) {
+ Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
+ return null;
+ }
+
+ // FIXME - support devices with different number of input and output ports
+ int subDevices = nativeGetSubdeviceCount(card, device);
+ if (subDevices <= 0) {
+ Log.e(TAG, "nativeGetSubdeviceCount failed");
+ return null;
+ }
+
+ // FIXME - support devices with different number of input and output ports
+ FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices);
+ if (fileDescriptors == null) {
+ Log.e(TAG, "nativeOpen failed");
+ return null;
+ }
+
+ Bundle properties = new Bundle();
+ properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, usbDevice.getManufacturerName());
+ properties.putString(MidiDeviceInfo.PROPERTY_MODEL, usbDevice.getProductName());
+ properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, usbDevice.getSerialNumber());
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
+ properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
+ MidiDeviceServer server = midiManager.createDeviceServer(subDevices, subDevices, properties,
+ false, MidiDeviceInfo.TYPE_USB);
+ if (server == null) {
+ Log.e(TAG, "createDeviceServer failed");
+ return null;
+ }
+
+ return new UsbMidiDevice(server, fileDescriptors, fileDescriptors);
+ }
+
+ private UsbMidiDevice(MidiDeviceServer server, FileDescriptor[] inputFiles,
+ FileDescriptor[] outputFiles) {
+ mServer = server;
+ int inputCount = inputFiles.length;
+ int outputCount = outputFiles.length;
+
+ mPollFDs = new StructPollfd[inputCount];
+ mInputStreams = new FileInputStream[inputCount];
+ for (int i = 0; i < inputCount; i++) {
+ FileDescriptor fd = inputFiles[i];
+ StructPollfd pollfd = new StructPollfd();
+ pollfd.fd = fd;
+ pollfd.events = (short)OsConstants.POLLIN;
+ mPollFDs[i] = pollfd;
+ mInputStreams[i] = new FileInputStream(fd);
+ }
+
+ mOutputStreams = new FileOutputStream[outputCount];
+ for (int i = 0; i < outputCount; i++) {
+ mOutputStreams[i] = new FileOutputStream(outputFiles[i]);
+ }
+
+ mOutputPortReceivers = new MidiReceiver[outputCount];
+ for (int port = 0; port < outputCount; port++) {
+ mOutputPortReceivers[port] = server.openOutputPortReceiver(port);
+ }
+
+ for (int port = 0; port < inputCount; port++) {
+ final int portNumberF = port;
+ MidiReceiver receiver = new MidiReceiver() {
+
+ @Override
+ public void onPost(byte[] data, int offset, int count, long timestamp)
+ throws IOException {
+ // FIXME - timestamps are ignored, future posting not supported yet.
+ mOutputStreams[portNumberF].write(data, offset, count);
+ }
+ };
+ MidiSender sender = server.openInputPortSender(port);
+ sender.connect(receiver);
+ }
+
+ new Thread() {
+ @Override
+ public void run() {
+ byte[] buffer = new byte[3];
+ try {
+ while (true) {
+ // look for a readable FileDescriptor
+ for (int index = 0; index < mPollFDs.length; index++) {
+ StructPollfd pfd = mPollFDs[index];
+ if ((pfd.revents & OsConstants.POLLIN) != 0) {
+ // clear readable flag
+ pfd.revents = 0;
+ int count = readMessage(buffer, index);
+ mOutputPortReceivers[index].onPost(buffer, 0, count, System.nanoTime());
+ }
+ }
+
+ // poll if none are readable
+ Os.poll(mPollFDs, -1 /* infinite timeout */);
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "reader thread exiting");
+ } catch (ErrnoException e) {
+ Log.d(TAG, "reader thread exiting");
+ }
+ }
+ }.start();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mServer.close();
+
+ for (int i = 0; i < mInputStreams.length; i++) {
+ mInputStreams[i].close();
+ }
+ for (int i = 0; i < mOutputStreams.length; i++) {
+ mOutputStreams[i].close();
+ }
+ }
+
+ private int readMessage(byte[] buffer, int index) throws IOException {
+ FileInputStream inputStream = mInputStreams[index];
+
+ if (inputStream.read(buffer, 0, 1) != 1) {
+ Log.e(TAG, "could not read command byte");
+ return -1;
+ }
+ int dataSize = MidiUtils.getMessageDataSize(buffer[0]);
+ if (dataSize < 0) {
+ return -1;
+ }
+ if (dataSize > 0) {
+ if (inputStream.read(buffer, 1, dataSize) != dataSize) {
+ Log.e(TAG, "could not read command data");
+ return -1;
+ }
+ }
+ return dataSize + 1;
+ }
+
+ private static native int nativeGetSubdeviceCount(int card, int device);
+ private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount);
+}