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>
+ * &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 @@
         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();