summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorMike Lockwood <lockwood@google.com>2015-03-05 00:00:31 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-03-05 00:01:33 +0000
commitc623ec973b5e8d1fd2ff4162d5de8701a6490121 (patch)
treed78b361229b131434f900b11b35e4df635c351b3 /media
parentef7aa4fc1901d5413bfa9a05f0a6c4f4d2d6575e (diff)
parent11fd96d6ff25bc1d710448eab545fe09da55a5f5 (diff)
downloadframeworks_base-c623ec973b5e8d1fd2ff4162d5de8701a6490121.zip
frameworks_base-c623ec973b5e8d1fd2ff4162d5de8701a6490121.tar.gz
frameworks_base-c623ec973b5e8d1fd2ff4162d5de8701a6490121.tar.bz2
Merge "MidiManager: Virtual MIDI devices are now implemented as Services"
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/midi/IMidiDeviceListener.aidl (renamed from media/java/android/media/midi/IMidiListener.aidl)2
-rw-r--r--media/java/android/media/midi/IMidiManager.aidl18
-rw-r--r--media/java/android/media/midi/MidiDeviceInfo.java34
-rw-r--r--media/java/android/media/midi/MidiDeviceServer.java254
-rw-r--r--media/java/android/media/midi/MidiDeviceService.java111
-rw-r--r--media/java/android/media/midi/MidiDispatcher.java89
-rw-r--r--media/java/android/media/midi/MidiManager.java116
-rw-r--r--media/java/android/media/midi/MidiOutputPort.java66
8 files changed, 428 insertions, 262 deletions
diff --git a/media/java/android/media/midi/IMidiListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl
index a4129e9..17d9bfd 100644
--- a/media/java/android/media/midi/IMidiListener.aidl
+++ b/media/java/android/media/midi/IMidiDeviceListener.aidl
@@ -19,7 +19,7 @@ package android.media.midi;
import android.media.midi.MidiDeviceInfo;
/** @hide */
-oneway interface IMidiListener
+oneway interface IMidiDeviceListener
{
void onDeviceAdded(in MidiDeviceInfo device);
void onDeviceRemoved(in MidiDeviceInfo device);
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index bba35f5..617b03e 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -16,8 +16,8 @@
package android.media.midi;
+import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceServer;
-import android.media.midi.IMidiListener;
import android.media.midi.MidiDeviceInfo;
import android.os.Bundle;
import android.os.IBinder;
@@ -28,14 +28,20 @@ interface IMidiManager
MidiDeviceInfo[] getDeviceList();
// for device creation & removal notifications
- void registerListener(IBinder token, in IMidiListener listener);
- void unregisterListener(IBinder token, in IMidiListener listener);
+ void registerListener(IBinder token, in IMidiDeviceListener listener);
+ void unregisterListener(IBinder token, in IMidiDeviceListener listener);
- // for communicating with MIDI devices
+ // for opening built-in MIDI devices
IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device);
- // for implementing virtual MIDI devices
+ // for registering built-in MIDI devices
MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
- int numOutputPorts, in Bundle properties, boolean isPrivate, int type);
+ int numOutputPorts, in Bundle properties, int type);
+
+ // for unregistering built-in MIDI devices
void unregisterDeviceServer(in IMidiDeviceServer server);
+
+ // used by MidiDeviceService to access the MidiDeviceInfo that was created based on its
+ // manifest's meta-data
+ MidiDeviceInfo getServiceDeviceInfo(String packageName, String className);
}
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index fd35052..b7756fd 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -50,6 +50,7 @@ public class MidiDeviceInfo implements Parcelable {
private final int mInputPortCount;
private final int mOutputPortCount;
private final Bundle mProperties;
+ private final boolean mIsPrivate;
/**
* Bundle key for the device's manufacturer name property.
@@ -83,6 +84,8 @@ public class MidiDeviceInfo implements Parcelable {
* 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}
+ *
+ * @hide
*/
public static final String PROPERTY_ALSA_CARD = "alsa_card";
@@ -90,20 +93,32 @@ public class MidiDeviceInfo implements Parcelable {
* 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}
+ *
+ * @hide
*/
public static final String PROPERTY_ALSA_DEVICE = "alsa_device";
/**
+ * {@link android.content.pm.ServiceInfo} for the service hosting the device implementation.
+ * Only set for Virtual MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ *
+ * @hide
+ */
+ public static final String PROPERTY_SERVICE_INFO = "service_info";
+
+ /**
* MidiDeviceInfo should only be instantiated by MidiService implementation
* @hide
*/
public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
- Bundle properties) {
+ Bundle properties, boolean isPrivate) {
mType = type;
mId = id;
mInputPortCount = numInputPorts;
mOutputPortCount = numOutputPorts;
mProperties = properties;
+ mIsPrivate = isPrivate;
}
/**
@@ -152,6 +167,16 @@ public class MidiDeviceInfo implements Parcelable {
return mProperties;
}
+ /**
+ * Returns true if the device is private. Private devices are only visible and accessible
+ * to clients with the same UID as the application that is hosting the device.
+ *
+ * @return true if the device is private
+ */
+ public boolean isPrivate() {
+ return mIsPrivate;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof MidiDeviceInfo) {
@@ -171,7 +196,8 @@ public class MidiDeviceInfo implements Parcelable {
return ("MidiDeviceInfo[mType=" + mType +
",mInputPortCount=" + mInputPortCount +
",mOutputPortCount=" + mOutputPortCount +
- ",mProperties=" + mProperties);
+ ",mProperties=" + mProperties +
+ ",mIsPrivate=" + mIsPrivate);
}
public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
@@ -182,7 +208,8 @@ public class MidiDeviceInfo implements Parcelable {
int inputPorts = in.readInt();
int outputPorts = in.readInt();
Bundle properties = in.readBundle();
- return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties);
+ boolean isPrivate = (in.readInt() == 1);
+ return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, isPrivate);
}
public MidiDeviceInfo[] newArray(int size) {
@@ -200,5 +227,6 @@ public class MidiDeviceInfo implements Parcelable {
parcel.writeInt(mInputPortCount);
parcel.writeInt(mOutputPortCount);
parcel.writeBundle(mProperties);
+ parcel.writeInt(mIsPrivate ? 1 : 0);
}
}
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 3317baa..24ef528 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -16,21 +16,22 @@
package android.media.midi;
+import android.os.IBinder;
+import android.os.Binder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.system.OsConstants;
import android.util.Log;
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.IOException;
-import java.util.ArrayList;
/**
- * This class is used to provide the implemention of MIDI device.
- * Applications may call {@link MidiManager#createDeviceServer}
- * to create an instance of this class to implement a virtual MIDI device.
+ * Internal class used for providing an implementation for a MIDI device.
*
- * CANDIDATE FOR PUBLIC API
* @hide
*/
public final class MidiDeviceServer implements Closeable {
@@ -40,64 +41,36 @@ public final class MidiDeviceServer implements Closeable {
// 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;
+ private final int mInputPortCount;
+ private final int mOutputPortCount;
- // receivers attached to our input ports
- private ArrayList<MidiReceiver>[] mInputPortReceivers;
+ // MidiReceivers for receiving data on our input ports
+ private final MidiReceiver[] mInputPortReceivers;
- // input ports for sending messages to our clients
- // we can have multiple outputs per port number
- private ArrayList<MidiInputPort>[] mOutputPortReceivers;
+ // MidiDispatchers for sending data on our output ports
+ private MidiDispatcher[] mOutputPortDispatchers;
- // 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()].clear();
- }
- }
- }
-
- // 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;
- }
- }
- }
+ // MidiOutputPorts for clients connected to our input ports
+ private final MidiOutputPort[] mInputPortOutputPorts;
// Binder interface stub for receiving connection requests from clients
private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
@Override
public ParcelFileDescriptor openInputPort(int portNumber) {
+ if (mDeviceInfo.isPrivate()) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ throw new SecurityException("Can't access private device from different UID");
+ }
+ }
+
if (portNumber < 0 || portNumber >= mInputPortCount) {
Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
return null;
}
- ParcelFileDescriptor result = null;
-
- synchronized (mInputPortSenders) {
- if (mInputPortSenders[portNumber] != null) {
+ synchronized (mInputPortOutputPorts) {
+ if (mInputPortOutputPorts[portNumber] != null) {
Log.d(TAG, "port " + portNumber + " already open");
return null;
}
@@ -105,47 +78,86 @@ public final class MidiDeviceServer implements Closeable {
try {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
- MidiOutputPort newOutputPort = new ServerOutputPort(pair[0], portNumber);
- mInputPortSenders[portNumber] = newOutputPort;
- result = pair[1];
-
- ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber];
- synchronized (receivers) {
- for (int i = 0; i < receivers.size(); i++) {
- newOutputPort.connect(receivers.get(i));
+ final MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
+ mInputPortOutputPorts[portNumber] = outputPort;
+ final int portNumberF = portNumber;
+ final MidiReceiver inputPortReceviver = mInputPortReceivers[portNumber];
+
+ outputPort.connect(new MidiReceiver() {
+ @Override
+ public void post(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ try {
+ inputPortReceviver.post(msg, offset, count, timestamp);
+ } catch (IOException e) {
+ IoUtils.closeQuietly(mInputPortOutputPorts[portNumberF]);
+ mInputPortOutputPorts[portNumberF] = null;
+ // FIXME also flush the receiver
+ }
}
- }
+ });
+
+ return pair[1];
} catch (IOException e) {
Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
return null;
}
}
-
- return result;
}
@Override
public ParcelFileDescriptor openOutputPort(int portNumber) {
+ if (mDeviceInfo.isPrivate()) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ throw new SecurityException("Can't access private device from different UID");
+ }
+ }
+
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;
- }
+
+ try {
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
+ OsConstants.SOCK_SEQPACKET);
+ final MidiInputPort inputPort = new MidiInputPort(pair[0], portNumber);
+ final MidiSender sender = mOutputPortDispatchers[portNumber].getSender();
+ sender.connect(new MidiReceiver() {
+ @Override
+ public void post(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ try {
+ inputPort.post(msg, offset, count, timestamp);
+ } catch (IOException e) {
+ IoUtils.closeQuietly(inputPort);
+ sender.disconnect(this);
+ // FIXME also flush the receiver?
+ }
+ }
+ });
+
+ return pair[1];
+ } catch (IOException e) {
+ Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
+ return null;
}
}
};
- /* package */ MidiDeviceServer(IMidiManager midiManager) {
+ /* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
+ int numOutputPorts) {
mMidiManager = midiManager;
+ mInputPortReceivers = inputPortReceivers;
+ mInputPortCount = inputPortReceivers.length;
+ mOutputPortCount = numOutputPorts;
+
+ mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];
+
+ mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
+ for (int i = 0; i < numOutputPorts; i++) {
+ mOutputPortDispatchers[i] = new MidiDispatcher();
+ }
}
/* package */ IMidiDeviceServer getBinderInterface() {
@@ -157,19 +169,6 @@ public final class MidiDeviceServer implements Closeable {
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
@@ -183,86 +182,13 @@ public final class MidiDeviceServer implements Closeable {
}
/**
- * 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}
+ * Returns an array of {@link MidiReceiver} for the device's output ports.
+ * Clients can use these receivers to send data out the device's output ports.
+ * @return array of MidiReceivers
*/
- 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 post(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).post(msg, offset, count, timestamp);
- }
- }
- }
- };
+ public MidiReceiver[] getOutputPortReceivers() {
+ MidiReceiver[] receivers = new MidiReceiver[mOutputPortCount];
+ System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
+ return receivers;
}
}
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
new file mode 100644
index 0000000..1d91be2
--- /dev/null
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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.media.midi;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * A service that implements a virtual MIDI device.
+ * Subclasses must implement the {@link #getInputPortReceivers} method to provide a
+ * list of {@link MidiReceiver}s to receive data sent to the device's input ports.
+ * Similarly, subclasses can call {@link #getOutputPortReceivers} to fetch a list
+ * of {@link MidiReceiver}s for sending data out the output ports.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * an intent filter with the {@link #SERVICE_INTERFACE} action
+ * and meta-data to describe the virtual device.
+ For example:</p>
+ * <pre>
+ * &lt;service android:name=".VirtualDeviceService"
+ * android:label="&#64;string/service_name">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.media.midi.MidiDeviceService" />
+ * &lt;/intent-filter>
+ * &lt;meta-data android:name="android.media.midi.MidiDeviceService"
+ android:resource="@xml/device_info" />
+ * &lt;/service></pre>
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+abstract public class MidiDeviceService extends Service {
+ private static final String TAG = "MidiDeviceService";
+
+ public static final String SERVICE_INTERFACE = "android.media.midi.MidiDeviceService";
+
+ private IMidiManager mMidiManager;
+ private MidiDeviceServer mServer;
+
+ @Override
+ public void onCreate() {
+ mMidiManager = IMidiManager.Stub.asInterface(
+ ServiceManager.getService(Context.MIDI_SERVICE));
+ MidiDeviceServer server;
+ try {
+ MidiDeviceInfo deviceInfo = mMidiManager.getServiceDeviceInfo(getPackageName(),
+ this.getClass().getName());
+ MidiReceiver[] inputPortReceivers = getInputPortReceivers();
+ if (inputPortReceivers == null) {
+ inputPortReceivers = new MidiReceiver[0];
+ }
+ server = new MidiDeviceServer(mMidiManager, inputPortReceivers,
+ deviceInfo.getOutputPortCount());
+ server.setDeviceInfo(deviceInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in IMidiManager.getServiceDeviceInfo");
+ server = null;
+ }
+ mServer = server;
+ }
+
+ /**
+ * Returns an array of {@link MidiReceiver} for the device's input ports.
+ * Subclasses must override this to provide the receivers which will receive
+ * data sent to the device's input ports. An empty array or null should be returned if
+ * the device has no input ports.
+ * @return array of MidiReceivers
+ */
+ abstract public MidiReceiver[] getInputPortReceivers();
+
+ /**
+ * Returns an array of {@link MidiReceiver} for the device's output ports.
+ * These can be used to send data out the device's output ports.
+ * @return array of MidiReceivers
+ */
+ public MidiReceiver[] getOutputPortReceivers() {
+ if (mServer == null) {
+ return null;
+ } else {
+ return mServer.getOutputPortReceivers();
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) {
+ return mServer.getBinderInterface().asBinder();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/media/java/android/media/midi/MidiDispatcher.java b/media/java/android/media/midi/MidiDispatcher.java
new file mode 100644
index 0000000..165061f
--- /dev/null
+++ b/media/java/android/media/midi/MidiDispatcher.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 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.media.midi;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Utility class for dispatching MIDI data to a list of {@link MidiReceiver}s.
+ * This class subclasses {@link MidiReceiver} and dispatches any data it receives
+ * to its receiver list. Any receivers that throw an exception upon receiving data will
+ * be automatically removed from the receiver list, but no IOException will be returned
+ * from the dispatcher's {@link #post} in that case.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public class MidiDispatcher implements MidiReceiver {
+
+ private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
+
+ private final MidiSender mSender = new MidiSender() {
+ /**
+ * Called to connect a {@link MidiReceiver} to the sender
+ *
+ * @param receiver the receiver to connect
+ */
+ public void connect(MidiReceiver receiver) {
+ mReceivers.add(receiver);
+ }
+
+ /**
+ * Called to disconnect a {@link MidiReceiver} from the sender
+ *
+ * @param receiver the receiver to disconnect
+ */
+ public void disconnect(MidiReceiver receiver) {
+ mReceivers.remove(receiver);
+ }
+ };
+
+ /**
+ * Returns whether this dispatcher contains any receivers.
+ * @return true if the receiver list is not empty
+ */
+ public boolean hasReceivers() {
+ return mReceivers.size() > 0;
+ }
+
+ /**
+ * Returns a {@link MidiSender} which is used to add and remove {@link MidiReceiver}s
+ * to the dispatcher's receiver list.
+ * @return the dispatcher's MidiSender
+ */
+ public MidiSender getSender() {
+ return mSender;
+ }
+
+ @Override
+ public void post(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ synchronized (mReceivers) {
+ for (int i = 0; i < mReceivers.size(); ) {
+ MidiReceiver receiver = mReceivers.get(i);
+ try {
+ receiver.post(msg, offset, count, timestamp);
+ i++; // increment only on success. on failure we remove the receiver
+ // so i should not be incremented
+ } catch (IOException e) {
+ // if the receiver fails we remove the receiver but do not propogate the exception
+ mSender.disconnect(receiver);
+ }
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index a502e3e..ca7d3c2 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -16,7 +16,11 @@
package android.media.midi;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.Bundle;
@@ -49,7 +53,7 @@ public class MidiManager {
new HashMap<DeviceCallback,DeviceListener>();
// Binder stub for receiving device notifications from MidiService
- private class DeviceListener extends IMidiListener.Stub {
+ private class DeviceListener extends IMidiDeviceListener.Stub {
private final DeviceCallback mCallback;
private final Handler mHandler;
@@ -88,22 +92,33 @@ public class MidiManager {
/**
* Callback class used for clients to receive MIDI device added and removed notifications
*/
- public static class DeviceCallback {
+ abstract public static class DeviceCallback {
/**
* Called to notify when a new MIDI device has been added
*
* @param device a {@link MidiDeviceInfo} for the newly added device
*/
- public void onDeviceAdded(MidiDeviceInfo device) {
- }
+ abstract public void onDeviceAdded(MidiDeviceInfo device);
/**
* Called to notify when a MIDI device has been removed
*
* @param device a {@link MidiDeviceInfo} for the removed device
*/
- public void onDeviceRemoved(MidiDeviceInfo device) {
- }
+ abstract public void onDeviceRemoved(MidiDeviceInfo device);
+ }
+
+ /**
+ * Callback class used for receiving the results of {@link #openDevice}
+ */
+ abstract public static class DeviceOpenCallback {
+ /**
+ * Called to respond to a {@link #openDevice} request
+ *
+ * @param deviceInfo the {@link MidiDeviceInfo} for the device to open
+ * @param device a {@link MidiDevice} for opened device, or null if opening failed
+ */
+ abstract public void onDeviceOpened(MidiDeviceInfo deviceInfo, MidiDevice device);
}
/**
@@ -163,33 +178,85 @@ public class MidiManager {
}
}
+ private void sendOpenDeviceResponse(final MidiDeviceInfo deviceInfo, final MidiDevice device,
+ final DeviceOpenCallback callback, Handler handler) {
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override public void run() {
+ callback.onDeviceOpened(deviceInfo, device);
+ }
+ });
+ } else {
+ callback.onDeviceOpened(deviceInfo, device);
+ }
+ }
+
/**
* Opens a MIDI device for reading and writing.
*
* @param deviceInfo a {@link android.media.midi.MidiDeviceInfo} to open
- * @return a {@link MidiDevice} object for the device
+ * @param callback a {@link #DeviceOpenCallback} to be called to receive the result
+ * @param handler the {@link android.os.Handler Handler} that will be used for delivering
+ * the result. If handler is null, then the thread used for the
+ * callback is unspecified.
*/
- public MidiDevice openDevice(MidiDeviceInfo deviceInfo) {
+ public void openDevice(MidiDeviceInfo deviceInfo, DeviceOpenCallback callback,
+ Handler handler) {
+ MidiDevice device = null;
try {
IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo);
if (server == null) {
- Log.e(TAG, "could not open device " + deviceInfo);
- return null;
+ ServiceInfo serviceInfo = (ServiceInfo)deviceInfo.getProperties().getParcelable(
+ MidiDeviceInfo.PROPERTY_SERVICE_INFO);
+ if (serviceInfo == null) {
+ Log.e(TAG, "no ServiceInfo for " + deviceInfo);
+ } else {
+ Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
+ intent.setComponent(new ComponentName(serviceInfo.packageName,
+ serviceInfo.name));
+ final MidiDeviceInfo deviceInfoF = deviceInfo;
+ final DeviceOpenCallback callbackF = callback;
+ final Handler handlerF = handler;
+ if (mContext.bindService(intent,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ IMidiDeviceServer server =
+ IMidiDeviceServer.Stub.asInterface(binder);
+ MidiDevice device = new MidiDevice(deviceInfoF, server);
+ sendOpenDeviceResponse(deviceInfoF, device, callbackF, handlerF);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // FIXME - anything to do here?
+ }
+ },
+ Context.BIND_AUTO_CREATE))
+ {
+ // return immediately to avoid calling sendOpenDeviceResponse below
+ return;
+ } else {
+ Log.e(TAG, "Unable to bind service: " + intent);
+ }
+ }
+ } else {
+ device = new MidiDevice(deviceInfo, server);
}
- return new MidiDevice(deviceInfo, server);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in openDevice");
}
- return null;
+ sendOpenDeviceResponse(deviceInfo, device, callback, handler);
}
/** @hide */
- public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
- Bundle properties, boolean isPrivate, int type) {
+ public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
+ int numOutputPorts, Bundle properties, int type) {
try {
- MidiDeviceServer server = new MidiDeviceServer(mService);
+ MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
+ numOutputPorts);
MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
- numInputPorts, numOutputPorts, properties, isPrivate, type);
+ inputPortReceivers.length, numOutputPorts, properties, type);
if (deviceInfo == null) {
Log.e(TAG, "registerVirtualDevice failed");
return null;
@@ -201,21 +268,4 @@ public class MidiManager {
return null;
}
}
-
- /**
- * Creates a new MIDI virtual device.
- *
- * @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 MidiDeviceServer} object to locally represent the device
- */
- public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
- Bundle properties, boolean isPrivate) {
- return createDeviceServer(numInputPorts, numOutputPorts, properties,
- isPrivate, MidiDeviceInfo.TYPE_VIRTUAL);
- }
-
}
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 83ddeeb..c195603 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -23,7 +23,6 @@ import libcore.io.IoUtils;
import java.io.FileInputStream;
import java.io.IOException;
-import java.util.ArrayList;
/**
* This class is used for receiving data from a port on a MIDI device
@@ -35,11 +34,7 @@ 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
+ private final MidiDispatcher mDispatcher = new MidiDispatcher();
// This thread reads MIDI events from a socket and distributes them to the list of
// MidiReceivers attached to this device.
@@ -47,7 +42,6 @@ public class MidiOutputPort extends MidiPort implements MidiSender {
@Override
public void run() {
byte[] buffer = new byte[MAX_PACKET_SIZE];
- ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
try {
while (true) {
@@ -55,77 +49,39 @@ public class MidiOutputPort extends MidiPort implements MidiSender {
int count = mInputStream.read(buffer);
if (count < 0) {
break;
+ // FIXME - inform receivers here?
}
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.post(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;
- }
- }
+ // dispatch to all our receivers
+ mDispatcher.post(buffer, offset, size, timestamp);
}
} catch (IOException e) {
- // report I/O failure
+ // FIXME report I/O failure?
Log.e(TAG, "read failed");
} finally {
IoUtils.closeQuietly(mInputStream);
- onIOException();
}
}
};
- /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
+ /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
super(portNumber);
mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ mThread.start();
}
- /**
- * Connects a {@link MidiReceiver} to the output port to allow receiving
- * MIDI messages from the port.
- *
- * @param receiver the receiver to connect
- */
+ @Override
public void connect(MidiReceiver receiver) {
- synchronized (mReceivers) {
- mReceivers.add(receiver);
- if (mReceiverCount++ == 0) {
- mThread.start();
- }
- }
+ mDispatcher.getSender().connect(receiver);
}
- /**
- * Disconnects a {@link MidiReceiver} from the output port.
- *
- * @param receiver the receiver to connect
- */
+ @Override
public void disconnect(MidiReceiver receiver) {
- synchronized (mReceivers) {
- if (mReceivers.remove(receiver)) {
- mReceiverCount--;
- }
- }
+ mDispatcher.getSender().disconnect(receiver);
}
@Override