summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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);
+}