Merge "MidiManager: Virtual MIDI devices are now implemented as Services"
diff --git a/Android.mk b/Android.mk
index f3f1acb..14dc4fa 100644
--- a/Android.mk
+++ b/Android.mk
@@ -333,8 +333,8 @@
media/java/android/media/IRingtonePlayer.aidl \
media/java/android/media/IVolumeController.aidl \
media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl \
+ media/java/android/media/midi/IMidiDeviceListener.aidl \
media/java/android/media/midi/IMidiDeviceServer.aidl \
- media/java/android/media/midi/IMidiListener.aidl \
media/java/android/media/midi/IMidiManager.aidl \
media/java/android/media/projection/IMediaProjection.aidl \
media/java/android/media/projection/IMediaProjectionCallback.aidl \
diff --git a/media/java/android/media/midi/IMidiListener.aidl b/media/java/android/media/midi/IMidiDeviceListener.aidl
similarity index 95%
rename from media/java/android/media/midi/IMidiListener.aidl
rename to 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 @@
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 @@
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 @@
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 @@
* 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
// MidiDeviceInfo for the device implemented by this server
private MidiDeviceInfo mDeviceInfo;
- private int mInputPortCount;
- private int mOutputPortCount;
+ private final int mInputPortCount;
+ private final int mOutputPortCount;
- // output ports for receiving messages from our clients
- // we can have only one per port number
- private MidiOutputPort[] mInputPortSenders;
+ // MidiReceivers for receiving data on our input ports
+ private final MidiReceiver[] mInputPortReceivers;
- // receivers attached to our input ports
- private ArrayList<MidiReceiver>[] mInputPortReceivers;
+ // MidiDispatchers for sending data on our output ports
+ private MidiDispatcher[] mOutputPortDispatchers;
- // 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()].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 @@
try {
ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
- MidiOutputPort newOutputPort = new ServerOutputPort(pair[0], portNumber);
- mInputPortSenders[portNumber] = newOutputPort;
- result = pair[1];
+ final MidiOutputPort outputPort = new MidiOutputPort(pair[0], portNumber);
+ mInputPortOutputPorts[portNumber] = outputPort;
+ final int portNumberF = portNumber;
+ final MidiReceiver inputPortReceviver = mInputPortReceivers[portNumber];
- ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber];
- synchronized (receivers) {
- for (int i = 0; i < receivers.size(); i++) {
- newOutputPort.connect(receivers.get(i));
+ 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 @@
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 @@
}
/**
- * Returns a {@link MidiDeviceInfo} object, which describes this device.
- *
- * @return the {@link MidiDeviceInfo} object
+ * 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 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 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>
+ * <service android:name=".VirtualDeviceService"
+ * android:label="@string/service_name">
+ * <intent-filter>
+ * <action android:name="android.media.midi.MidiDeviceService" />
+ * </intent-filter>
+ * <meta-data android:name="android.media.midi.MidiDeviceService"
+ android:resource="@xml/device_info" />
+ * </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 @@
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 @@
/**
* 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 @@
}
}
+ 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 @@
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 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 @@
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 @@
@Override
public void run() {
byte[] buffer = new byte[MAX_PACKET_SIZE];
- ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
try {
while (true) {
@@ -55,77 +49,39 @@
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
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
index 59b82da..7f98b30 100644
--- a/services/core/java/com/android/server/MidiService.java
+++ b/services/core/java/com/android/server/MidiService.java
@@ -17,10 +17,18 @@
package com.android.server;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.XmlResourceParser;
+import android.media.midi.IMidiDeviceListener;
import android.media.midi.IMidiDeviceServer;
-import android.media.midi.IMidiListener;
import android.media.midi.IMidiManager;
import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceService;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -28,12 +36,17 @@
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
public class MidiService extends IMidiManager.Stub {
private static final String TAG = "MidiService";
@@ -53,6 +66,27 @@
// used for assigning IDs to MIDI devices
private int mNextDeviceId = 1;
+ private final PackageManager mPackageManager;
+
+ // PackageMonitor for listening to package changes
+ private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ addPackageDeviceServers(packageName);
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ removePackageDeviceServers(packageName);
+ addPackageDeviceServers(packageName);
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ removePackageDeviceServers(packageName);
+ }
+ };
+
private final class Client implements IBinder.DeathRecipient {
// Binder token for this client
private final IBinder mToken;
@@ -61,7 +95,8 @@
// This client's PID
private final int mPid;
// List of all receivers for this client
- private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
+ private final ArrayList<IMidiDeviceListener> mListeners
+ = new ArrayList<IMidiDeviceListener>();
public Client(IBinder token) {
mToken = token;
@@ -73,11 +108,11 @@
return mUid;
}
- public void addListener(IMidiListener listener) {
+ public void addListener(IMidiDeviceListener listener) {
mListeners.add(listener);
}
- public void removeListener(IMidiListener listener) {
+ public void removeListener(IMidiDeviceListener listener) {
mListeners.remove(listener);
if (mListeners.size() == 0) {
removeClient(mToken);
@@ -90,7 +125,7 @@
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
- for (IMidiListener listener : mListeners) {
+ for (IMidiDeviceListener listener : mListeners) {
listener.onDeviceAdded(deviceInfo);
}
} catch (RemoteException e) {
@@ -104,7 +139,7 @@
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
- for (IMidiListener listener : mListeners) {
+ for (IMidiDeviceListener listener : mListeners) {
listener.onDeviceRemoved(deviceInfo);
}
} catch (RemoteException e) {
@@ -152,16 +187,17 @@
private final class Device implements IBinder.DeathRecipient {
private final IMidiDeviceServer mServer;
private final MidiDeviceInfo mDeviceInfo;
- // UID of device creator
+ // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only)
+ private final ServiceInfo mServiceInfo;
+ // UID of device implementation
private final int mUid;
- // PID of device creator
- private final int mPid;
- public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo) {
+ public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
+ ServiceInfo serviceInfo, int uid) {
mServer = server;
mDeviceInfo = deviceInfo;
- mUid = Binder.getCallingUid();
- mPid = Binder.getCallingPid();
+ mServiceInfo = serviceInfo;
+ mUid = uid;
}
public MidiDeviceInfo getDeviceInfo() {
@@ -172,13 +208,20 @@
return mServer;
}
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ public String getPackageName() {
+ return (mServiceInfo == null ? null : mServiceInfo.packageName);
+ }
+
public boolean isUidAllowed(int uid) {
- // FIXME
- return true;
+ return (!mDeviceInfo.isPrivate() || mUid == uid);
}
public void binderDied() {
- synchronized (mDevicesByServer) {
+ synchronized (mDevicesByInfo) {
removeDeviceLocked(this);
}
}
@@ -189,32 +232,59 @@
sb.append(mDeviceInfo);
sb.append(" UID: ");
sb.append(mUid);
- sb.append(" PID: ");
- sb.append(mPid);
return sb.toString();
}
}
public MidiService(Context context) {
mContext = context;
- }
+ mPackageManager = context.getPackageManager();
+ mPackageMonitor.register(context, null, true);
- public void registerListener(IBinder token, IMidiListener listener) {
+ Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
+ List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent,
+ PackageManager.GET_META_DATA);
+ if (resolveInfos != null) {
+ int count = resolveInfos.size();
+ for (int i = 0; i < count; i++) {
+ ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
+ if (serviceInfo != null) {
+ addPackageDeviceServer(serviceInfo);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void registerListener(IBinder token, IMidiDeviceListener listener) {
Client client = getClient(token);
if (client == null) return;
client.addListener(listener);
}
- public void unregisterListener(IBinder token, IMidiListener listener) {
+ @Override
+ public void unregisterListener(IBinder token, IMidiDeviceListener listener) {
Client client = getClient(token);
if (client == null) return;
client.removeListener(listener);
}
public MidiDeviceInfo[] getDeviceList() {
- return mDevicesByInfo.keySet().toArray(new MidiDeviceInfo[0]);
+ ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
+ int uid = Binder.getCallingUid();
+
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ if (device.isUidAllowed(uid)) {
+ deviceInfos.add(device.getDeviceInfo());
+ }
+ }
+ }
+
+ return deviceInfos.toArray(new MidiDeviceInfo[0]);
}
+ @Override
public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
Device device = mDevicesByInfo.get(deviceInfo);
if (device == null) {
@@ -229,28 +299,64 @@
return device.getDeviceServer();
}
+ @Override
public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
- int numOutputPorts, Bundle properties, boolean isPrivate, int type) {
+ int numOutputPorts, Bundle properties, 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 (mDevicesByInfo) {
+ return addDeviceLocked(type, numInputPorts, numOutputPorts, properties,
+ server, null, false, -1);
+ }
+ }
- synchronized (mDevicesByServer) {
- int id = mNextDeviceId++;
- deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, properties);
+ @Override
+ public void unregisterDeviceServer(IMidiDeviceServer server) {
+ synchronized (mDevicesByInfo) {
+ Device device = mDevicesByServer.get(server.asBinder());
+ if (device != null) {
+ removeDeviceLocked(device);
+ }
+ }
+ }
+
+ @Override
+ public MidiDeviceInfo getServiceDeviceInfo(String packageName, String className) {
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ ServiceInfo serviceInfo = device.getServiceInfo();
+ if (serviceInfo != null &&
+ packageName.equals(serviceInfo.packageName) &&
+ className.equals(serviceInfo.name)) {
+ return device.getDeviceInfo();
+ }
+ }
+ return null;
+ }
+ }
+
+ // synchronize on mDevicesByInfo
+ private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
+ Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo,
+ boolean isPrivate, int uid) {
+
+ int id = mNextDeviceId++;
+ MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
+ properties, isPrivate);
+ Device device = new Device(server, deviceInfo, serviceInfo, uid);
+
+ if (server != null) {
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);
+ mDevicesByServer.put(binder, device);
}
+ mDevicesByInfo.put(deviceInfo, device);
synchronized (mClients) {
for (Client c : mClients.values()) {
@@ -261,16 +367,13 @@
return deviceInfo;
}
- public void unregisterDeviceServer(IMidiDeviceServer server) {
- synchronized (mDevicesByServer) {
- removeDeviceLocked(mDevicesByServer.get(server.asBinder()));
- }
- }
-
- // synchronize on mDevicesByServer
+ // synchronize on mDevicesByInfo
private void removeDeviceLocked(Device device) {
- if (mDevicesByServer.remove(device.getDeviceServer().asBinder()) != null) {
- mDevicesByInfo.remove(device.getDeviceInfo());
+ if (mDevicesByInfo.remove(device.getDeviceInfo()) != null) {
+ IMidiDeviceServer server = device.getDeviceServer();
+ if (server != null) {
+ mDevicesByServer.remove(server);
+ }
synchronized (mClients) {
for (Client c : mClients.values()) {
@@ -280,6 +383,134 @@
}
}
+ private void addPackageDeviceServers(String packageName) {
+ PackageInfo info;
+
+ try {
+ info = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
+ return;
+ }
+
+ ServiceInfo[] services = info.services;
+ if (services == null) return;
+ for (int i = 0; i < services.length; i++) {
+ addPackageDeviceServer(services[i]);
+ }
+ }
+
+ private void addPackageDeviceServer(ServiceInfo serviceInfo) {
+ XmlResourceParser parser = null;
+
+ try {
+ parser = serviceInfo.loadXmlMetaData(mPackageManager,
+ MidiDeviceService.SERVICE_INTERFACE);
+ if (parser == null) return;
+
+ Bundle properties = null;
+ int numInputPorts = 0;
+ int numOutputPorts = 0;
+ boolean isPrivate = false;
+
+ while (true) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ break;
+ } else if (eventType == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ if ("device".equals(tagName)) {
+ if (properties != null) {
+ Log.w(TAG, "nested <device> elements in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ properties = new Bundle();
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_SERVICE_INFO, serviceInfo);
+ numInputPorts = 0;
+ numOutputPorts = 0;
+ isPrivate = false;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ if ("private".equals(name)) {
+ isPrivate = "true".equals(value);
+ } else {
+ properties.putString(name, value);
+ }
+ }
+ } else if ("input-port".equals(tagName)) {
+ if (properties == null) {
+ Log.w(TAG, "<input-port> outside of <device> in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ numInputPorts++;
+ // TODO - add support for port properties
+ } else if ("output-port".equals(tagName)) {
+ if (properties == null) {
+ Log.w(TAG, "<output-port> outside of <device> in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ numOutputPorts++;
+ // TODO - add support for port properties
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ String tagName = parser.getName();
+ if ("device".equals(tagName)) {
+ if (properties != null) {
+ if (numInputPorts == 0 && numOutputPorts == 0) {
+ Log.w(TAG, "<device> with no ports in metadata for "
+ + serviceInfo.packageName);
+ continue;
+ }
+
+ int uid = -1;
+ if (isPrivate) {
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+ serviceInfo.packageName, 0);
+ uid = appInfo.uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "could not fetch ApplicationInfo for "
+ + serviceInfo.packageName);
+ continue;
+ }
+ }
+
+ synchronized (mDevicesByInfo) {
+ addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL,
+ numInputPorts, numOutputPorts, properties,
+ null, serviceInfo, isPrivate, uid);
+ }
+ // setting properties to null signals that we are no longer
+ // processing a <device>
+ properties = null;
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + serviceInfo.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ private void removePackageDeviceServers(String packageName) {
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ if (packageName.equals(device.getPackageName())) {
+ removeDeviceLocked(device);
+ }
+ }
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index e17abc0..a6e9677 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceServer;
+import android.media.midi.MidiDispatcher;
import android.media.midi.MidiManager;
import android.media.midi.MidiPort;
import android.media.midi.MidiReceiver;
@@ -30,6 +31,8 @@
import android.system.StructPollfd;
import android.util.Log;
+import libcore.io.IoUtils;
+
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -39,8 +42,9 @@
public final class UsbMidiDevice implements Closeable {
private static final String TAG = "UsbMidiDevice";
- private final MidiDeviceServer mServer;
- private final MidiReceiver[] mOutputPortReceivers;
+ private MidiDeviceServer mServer;
+
+ private final MidiReceiver[] mInputPortReceivers;
// for polling multiple FileDescriptors for MIDI events
private final StructPollfd[] mPollFDs;
@@ -50,12 +54,6 @@
private final FileOutputStream[] mOutputStreams;
public static UsbMidiDevice create(Context context, Bundle properties, 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) {
@@ -70,19 +68,16 @@
return null;
}
- MidiDeviceServer server = midiManager.createDeviceServer(subDevices, subDevices, properties,
- false, MidiDeviceInfo.TYPE_USB);
- if (server == null) {
+ UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors, fileDescriptors);
+ if (!midiDevice.register(context, properties)) {
+ IoUtils.closeQuietly(midiDevice);
Log.e(TAG, "createDeviceServer failed");
return null;
}
-
- return new UsbMidiDevice(server, fileDescriptors, fileDescriptors);
+ return midiDevice;
}
- private UsbMidiDevice(MidiDeviceServer server, FileDescriptor[] inputFiles,
- FileDescriptor[] outputFiles) {
- mServer = server;
+ private UsbMidiDevice(FileDescriptor[] inputFiles, FileDescriptor[] outputFiles) {
int inputCount = inputFiles.length;
int outputCount = outputFiles.length;
@@ -102,26 +97,36 @@
mOutputStreams[i] = new FileOutputStream(outputFiles[i]);
}
- mOutputPortReceivers = new MidiReceiver[outputCount];
- for (int port = 0; port < outputCount; port++) {
- mOutputPortReceivers[port] = server.openOutputPortReceiver(port);
- }
-
+ mInputPortReceivers = new MidiReceiver[inputCount];
for (int port = 0; port < inputCount; port++) {
- final int portNumberF = port;
- MidiReceiver receiver = new MidiReceiver() {
-
+ final int portF = port;
+ mInputPortReceivers[port] = new MidiReceiver() {
@Override
public void post(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);
+ mOutputStreams[portF].write(data, offset, count);
}
};
- MidiSender sender = server.openInputPortSender(port);
- sender.connect(receiver);
+ }
+ }
+
+ private boolean register(Context context, Bundle properties) {
+ MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+ if (midiManager == null) {
+ Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
+ return false;
}
+ int outputCount = mOutputStreams.length;
+ mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount,
+ properties, MidiDeviceInfo.TYPE_USB);
+ if (mServer == null) {
+ return false;
+ }
+ final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
+
+ // FIXME can we only run this when we have a dispatcher that has listeners?
new Thread() {
@Override
public void run() {
@@ -137,8 +142,8 @@
pfd.revents = 0;
int count = mInputStreams[index].read(buffer);
- mOutputPortReceivers[index].post(buffer, 0, count,
- System.nanoTime());
+ long timestamp = System.nanoTime();
+ outputReceivers[index].post(buffer, 0, count, timestamp);
} else if ((pfd.revents & (OsConstants.POLLERR
| OsConstants.POLLHUP)) != 0) {
done = true;
@@ -155,11 +160,15 @@
}
}
}.start();
+
+ return true;
}
@Override
public void close() throws IOException {
- mServer.close();
+ if (mServer != null) {
+ mServer.close();
+ }
for (int i = 0; i < mInputStreams.length; i++) {
mInputStreams[i].close();