MidiManager updates:

MIDI ports are now implemented as file descriptors directly between the sender
and receiver, so the MidiService is no longer in the message path.

To facilitate the above, each port has its own file descriptor, rather than multiplexing
all ports on a device through a single socket.

Added a new class MidiDeviceServer, which is used by implementors of MIDI devices.
This replaces the MidiVirtualDevice class (which only was included in changes that were reviewed but never submitted).

The USB MIDI implementation has moved from the MIDI service to the USB service.
The USB MIDI implementation uses MidiDeviceServer as its interface, so we now have a common
interface for all MIDI device implementations.

Change-Id: I8effd1583f344beb6c940c3a24dbf20b477a6436
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
new file mode 100644
index 0000000..38f1cb8
--- /dev/null
+++ b/services/core/java/com/android/server/MidiService.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions an
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.midi.IMidiDeviceServer;
+import android.midi.IMidiListener;
+import android.midi.IMidiManager;
+import android.midi.MidiDeviceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class MidiService extends IMidiManager.Stub {
+    private static final String TAG = "MidiService";
+
+    private final Context mContext;
+
+    // list of all our clients, keyed by Binder token
+    private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
+
+    // list of all devices, keyed by MidiDeviceInfo
+    private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo
+            = new HashMap<MidiDeviceInfo, Device>();
+
+    // list of all devices, keyed by IMidiDeviceServer
+    private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>();
+
+    // used for assigning IDs to MIDI devices
+    private int mNextDeviceId = 1;
+
+    private final class Client implements IBinder.DeathRecipient {
+        // Binder token for this client
+        private final IBinder mToken;
+        // This client's UID
+        private final int mUid;
+        // This client's PID
+        private final int mPid;
+        // List of all receivers for this client
+        private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
+
+        public Client(IBinder token) {
+            mToken = token;
+            mUid = Binder.getCallingUid();
+            mPid = Binder.getCallingPid();
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public void addListener(IMidiListener listener) {
+            mListeners.add(listener);
+        }
+
+        public void removeListener(IMidiListener listener) {
+            mListeners.remove(listener);
+            if (mListeners.size() == 0) {
+                removeClient(mToken);
+            }
+        }
+
+        public void deviceAdded(Device device) {
+            // ignore private devices that our client cannot access
+            if (!device.isUidAllowed(mUid)) return;
+
+            MidiDeviceInfo deviceInfo = device.getDeviceInfo();
+            try {
+                for (IMidiListener listener : mListeners) {
+                    listener.onDeviceAdded(deviceInfo);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "remote exception", e);
+            }
+        }
+
+        public void deviceRemoved(Device device) {
+            // ignore private devices that our client cannot access
+            if (!device.isUidAllowed(mUid)) return;
+
+            MidiDeviceInfo deviceInfo = device.getDeviceInfo();
+            try {
+                for (IMidiListener listener : mListeners) {
+                    listener.onDeviceRemoved(deviceInfo);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "remote exception", e);
+            }
+        }
+
+        public void binderDied() {
+            removeClient(mToken);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("Client: UID: ");
+            sb.append(mUid);
+            sb.append(" PID: ");
+            sb.append(mPid);
+            sb.append(" listener count: ");
+            sb.append(mListeners.size());
+            return sb.toString();
+        }
+    }
+
+    private Client getClient(IBinder token) {
+        synchronized (mClients) {
+            Client client = mClients.get(token);
+            if (client == null) {
+                client = new Client(token);
+
+                try {
+                    token.linkToDeath(client, 0);
+                } catch (RemoteException e) {
+                    return null;
+                }
+                mClients.put(token, client);
+            }
+            return client;
+        }
+    }
+
+    private void removeClient(IBinder token) {
+        mClients.remove(token);
+    }
+
+    private final class Device implements IBinder.DeathRecipient {
+        private final IMidiDeviceServer mServer;
+        private final MidiDeviceInfo mDeviceInfo;
+        // UID of device creator
+        private final int mUid;
+        // PID of device creator
+        private final int mPid;
+
+        public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo) {
+            mServer = server;
+            mDeviceInfo = deviceInfo;
+            mUid = Binder.getCallingUid();
+            mPid = Binder.getCallingPid();
+        }
+
+        public MidiDeviceInfo getDeviceInfo() {
+            return mDeviceInfo;
+        }
+
+        public IMidiDeviceServer getDeviceServer() {
+            return mServer;
+        }
+
+        public boolean isUidAllowed(int uid) {
+            // FIXME
+            return true;
+        }
+
+        public void binderDied() {
+            synchronized (mDevicesByServer) {
+                removeDeviceLocked(this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("Device: ");
+            sb.append(mDeviceInfo);
+            sb.append(" UID: ");
+            sb.append(mUid);
+            sb.append(" PID: ");
+            sb.append(mPid);
+            return sb.toString();
+        }
+    }
+
+    public MidiService(Context context) {
+        mContext = context;
+    }
+
+    public void registerListener(IBinder token, IMidiListener listener) {
+        Client client = getClient(token);
+        if (client == null) return;
+        client.addListener(listener);
+    }
+
+    public void unregisterListener(IBinder token, IMidiListener listener) {
+        Client client = getClient(token);
+        if (client == null) return;
+        client.removeListener(listener);
+    }
+
+    public MidiDeviceInfo[] getDeviceList() {
+        return mDevicesByInfo.keySet().toArray(new MidiDeviceInfo[0]);
+    }
+
+    public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
+        Device device = mDevicesByInfo.get(deviceInfo);
+        if (device == null) {
+            Log.e(TAG, "device not found in openDevice: " + deviceInfo);
+            return null;
+        }
+
+        if (!device.isUidAllowed(Binder.getCallingUid())) {
+            throw new SecurityException("Attempt to open private device with wrong UID");
+        }
+
+        return device.getDeviceServer();
+    }
+
+    public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
+            int numOutputPorts, Bundle properties, boolean isPrivate, int type) {
+        if (type != MidiDeviceInfo.TYPE_VIRTUAL && Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("only system can create non-virtual devices");
+        }
+
+        MidiDeviceInfo deviceInfo;
+        Device device;
+
+        synchronized (mDevicesByServer) {
+            int id = mNextDeviceId++;
+            deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, properties);
+            IBinder binder = server.asBinder();
+            device = new Device(server, deviceInfo);
+            try {
+                binder.linkToDeath(device, 0);
+            } catch (RemoteException e) {
+                return null;
+            }
+            mDevicesByInfo.put(deviceInfo, device);
+            mDevicesByServer.put(server.asBinder(), device);
+        }
+
+        synchronized (mClients) {
+            for (Client c : mClients.values()) {
+                c.deviceAdded(device);
+            }
+        }
+
+        return deviceInfo;
+    }
+
+    public void unregisterDeviceServer(IMidiDeviceServer server) {
+        synchronized (mDevicesByServer) {
+            removeDeviceLocked(mDevicesByServer.get(server.asBinder()));
+        }
+    }
+
+    // synchronize on mDevicesByServer
+    private void removeDeviceLocked(Device device) {
+        if (mDevicesByServer.remove(device.getDeviceServer().asBinder()) != null) {
+            mDevicesByInfo.remove(device.getDeviceInfo());
+
+            synchronized (mClients) {
+                for (Client c : mClients.values()) {
+                    c.deviceRemoved(device);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+
+        pw.println("MIDI Manager State:");
+        pw.increaseIndent();
+
+        pw.println("Devices:");
+        pw.increaseIndent();
+        for (Device device : mDevicesByInfo.values()) {
+            pw.println(device.toString());
+        }
+        pw.decreaseIndent();
+
+        pw.println("Clients:");
+        pw.increaseIndent();
+        for (Client client : mClients.values()) {
+            pw.println(client.toString());
+        }
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/midi/MidiDeviceBase.java b/services/core/java/com/android/server/midi/MidiDeviceBase.java
deleted file mode 100644
index 4289584..0000000
--- a/services/core/java/com/android/server/midi/MidiDeviceBase.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.Binder;
-import android.os.ParcelFileDescriptor;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Abstract internal base class for entities in MidiService.
- * This class contains two threads for reading and writing MIDI events.
- * On one end we have the readMessage() and writeMessage() methods, which must be
- * implemented by a subclass. On the other end we have file descriptors for sockets
- * attached to client applications.
- */
-abstract class MidiDeviceBase {
-    private static final String TAG = "MidiDeviceBase";
-
-    final MidiDeviceInfo mDeviceInfo;
-    private ReaderThread mReaderThread;
-    private WriterThread mWriterThread;
-    private ParcelFileDescriptor mParcelFileDescriptor;
-
-    // Reads MIDI messages from readMessage() and write them to one or more clients.
-    private class ReaderThread extends Thread {
-        private final ArrayList<FileOutputStream> mOutputStreams =
-                new ArrayList<FileOutputStream>();
-
-        @Override
-        public void run() {
-            byte[] buffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
-
-            while (true) {
-                try {
-                    int count = readMessage(buffer);
-
-                    if (count > 0) {
-                        synchronized (mOutputStreams) {
-                            for (int i = 0; i < mOutputStreams.size(); i++) {
-                                FileOutputStream fos = mOutputStreams.get(i);
-                                try {
-                                    fos.write(buffer, 0, count);
-                                } catch (IOException e) {
-                                    Log.e(TAG, "write failed", e);
-                                    mOutputStreams.remove(fos);
-                                }
-                            }
-                        }
-                    }
-                } catch (IOException e) {
-                    Log.e(TAG, "read failed", e);
-                    break;
-                }
-            }
-        }
-
-        public void addListener(FileOutputStream fos) {
-            synchronized (mOutputStreams) {
-                mOutputStreams.add(fos);
-            }
-        }
-
-        public void removeListener(FileOutputStream fos) {
-            synchronized (mOutputStreams) {
-                mOutputStreams.remove(fos);
-            }
-        }
-    }
-
-    // Reads MIDI messages from our client and writes them by calling writeMessage()
-    private class WriterThread extends Thread {
-        private final FileInputStream mInputStream;
-
-        public WriterThread(FileInputStream fis) {
-            mInputStream = fis;
-        }
-
-        @Override
-        public void run() {
-            byte[] buffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
-
-            while (true) {
-                try {
-                    int count = mInputStream.read(buffer);
-                    writeMessage(buffer, count);
-                } catch (IOException e) {
-                    Log.e(TAG, "WriterThread failed", e);
-                    break;
-                }
-            }
-        }
-    }
-
-    public MidiDeviceBase(MidiDeviceInfo info) {
-        mDeviceInfo = info;
-    }
-
-    public MidiDeviceInfo getInfo() {
-        return mDeviceInfo;
-    }
-
-    public ParcelFileDescriptor getFileDescriptor() {
-        synchronized (this) {
-            if (mReaderThread == null) {
-                if (!open()) {
-                    return null;
-                }
-                mReaderThread = new ReaderThread();
-                mReaderThread.start();
-            }
-        }
-
-        try {
-            ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
-                                                            OsConstants.SOCK_SEQPACKET);
-         mParcelFileDescriptor = pair[0];
-         FileOutputStream fos = new FileOutputStream(mParcelFileDescriptor.getFileDescriptor());
-            mReaderThread.addListener(fos);
-
-            // return an error if the device is already open for writing?
-            if (mWriterThread == null) {
-                FileInputStream fis = new FileInputStream(
-                        mParcelFileDescriptor.getFileDescriptor());
-                mWriterThread = new WriterThread(fis);
-                mWriterThread.start();
-            }
-
-            return pair[1];
-        } catch (IOException e) {
-            Log.e(TAG, "could not create ParcelFileDescriptor pair", e);
-            return null;
-        }
-    }
-
-    abstract boolean open();
-
-    void close() {
-        try {
-            if (mParcelFileDescriptor != null) {
-                mParcelFileDescriptor.close();
-            }
-        } catch (IOException e) {
-        }
-    }
-
-    abstract int readMessage(byte[] buffer) throws IOException;
-    abstract void writeMessage(byte[] buffer, int count) throws IOException;
-}
diff --git a/services/core/java/com/android/server/midi/MidiService.java b/services/core/java/com/android/server/midi/MidiService.java
deleted file mode 100644
index 29a8339..0000000
--- a/services/core/java/com/android/server/midi/MidiService.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.content.Context;
-import android.hardware.usb.UsbDevice;
-import android.midi.IMidiListener;
-import android.midi.IMidiManager;
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class MidiService extends IMidiManager.Stub {
-    private static final String TAG = "MidiService";
-
-    private final Context mContext;
-
-    // list of all our clients, keyed by Binder token
-    private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
-
-    // list of all devices, keyed by ID
-    private final HashMap<Integer, MidiDeviceBase> mDevices
-            = new HashMap<Integer, MidiDeviceBase>();
-
-    // list of all USB devices, keyed by USB device.
-    private final HashMap<UsbDevice, UsbMidiDevice> mUsbDevices
-            = new HashMap<UsbDevice, UsbMidiDevice>();
-
-    // used for assigning IDs to MIDI devices
-    private int mNextDeviceId = 1;
-
-    private final class Client implements IBinder.DeathRecipient {
-        private final IBinder mToken;
-        private final ArrayList<IMidiListener> mListeners = new ArrayList<IMidiListener>();
-        private final ArrayList<MidiDeviceBase> mVirtualDevices = new ArrayList<MidiDeviceBase>();
-
-        public Client(IBinder token) {
-            mToken = token;
-        }
-
-        public void close() {
-            for (MidiDeviceBase device : mVirtualDevices) {
-                device.close();
-            }
-            mVirtualDevices.clear();
-        }
-
-        public void addListener(IMidiListener listener) {
-            mListeners.add(listener);
-        }
-
-        public void removeListener(IMidiListener listener) {
-            mListeners.remove(listener);
-            if (mListeners.size() == 0 && mVirtualDevices.size() == 0) {
-                removeClient(mToken);
-            }
-        }
-
-        public void addVirtualDevice(MidiDeviceBase device) {
-            mVirtualDevices.add(device);
-        }
-
-        public void removeVirtualDevice(MidiDeviceBase device) {
-            mVirtualDevices.remove(device);
-        }
-
-        public void deviceAdded(MidiDeviceInfo device) {
-            try {
-                for (IMidiListener listener : mListeners) {
-                    listener.onDeviceAdded(device);
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "remote exception", e);
-            }
-        }
-
-        public void deviceRemoved(MidiDeviceInfo device) {
-            try {
-                for (IMidiListener listener : mListeners) {
-                    listener.onDeviceRemoved(device);
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "remote exception", e);
-            }
-        }
-
-        public void binderDied() {
-            removeClient(mToken);
-        }
-    }
-
-
-    private Client getClient(IBinder token) {
-        synchronized (mClients) {
-            Client client = mClients.get(token);
-            if (client == null) {
-                client = new Client(token);
-
-                try {
-                    token.linkToDeath(client, 0);
-                } catch (RemoteException e) {
-                    return null;
-                }
-                mClients.put(token, client);
-            }
-            return client;
-        }
-    }
-
-    private void removeClient(IBinder token) {
-        synchronized (mClients) {
-            Client client = mClients.remove(token);
-            if (client != null) {
-                client.close();
-            }
-        }
-    }
-
-    public MidiService(Context context) {
-        mContext = context;
-    }
-
-    public void registerListener(IBinder token, IMidiListener listener) {
-        Client client = getClient(token);
-        if (client == null) return;
-        client.addListener(listener);
-    }
-
-    public void unregisterListener(IBinder token, IMidiListener listener) {
-        Client client = getClient(token);
-        if (client == null) return;
-        client.removeListener(listener);
-    }
-
-    public MidiDeviceInfo[] getDeviceList() {
-        ArrayList<MidiDeviceInfo> infos = new ArrayList<MidiDeviceInfo>();
-        for (MidiDeviceBase device : mDevices.values()) {
-            infos.add(device.getInfo());
-        }
-        return infos.toArray(new MidiDeviceInfo[0]);
-    }
-
-    public ParcelFileDescriptor openDevice(IBinder token, MidiDeviceInfo deviceInfo) {
-        MidiDeviceBase device = mDevices.get(deviceInfo.getId());
-        if (device == null) {
-            Log.e(TAG, "device not found in openDevice: " + deviceInfo);
-            return null;
-        }
-
-        return device.getFileDescriptor();
-    }
-
-    public MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
-            Bundle properties) {
-        VirtualMidiDevice device;
-        Client client = getClient(token);
-        if (client == null) return null;
-
-        synchronized (mDevices) {
-            int id = mNextDeviceId++;
-            MidiDeviceInfo deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_VIRTUAL, id,
-                    numInputPorts, numOutputPorts, properties);
-
-            device = new VirtualMidiDevice(deviceInfo);
-            if (!device.open()) {
-                return null;
-            }
-            mDevices.put(id, device);
-            client.addVirtualDevice(device);
-        }
-
-        synchronized (mClients) {
-            MidiDeviceInfo deviceInfo = device.getInfo();
-            for (Client c : mClients.values()) {
-                c.deviceAdded(deviceInfo);
-            }
-        }
-
-        return device.getProxy();
-    }
-
-    public void unregisterVirtualDevice(IBinder token, MidiDeviceInfo deviceInfo) {
-        Client client = getClient(token);
-        if (client == null) return;
-
-        MidiDeviceBase device;
-        synchronized (mDevices) {
-            device = mDevices.remove(deviceInfo.getId());
-        }
-
-        if (device != null) {
-            client.removeVirtualDevice(device);
-            device.close();
-
-            synchronized (mClients) {
-                for (Client c : mClients.values()) {
-                    c.deviceRemoved(deviceInfo);
-                }
-            }
-        }
-    }
-
-    // called by UsbAudioManager to notify of new USB MIDI devices
-    public void alsaDeviceAdded(int card, int device, UsbDevice usbDevice) {
-        Log.d(TAG, "alsaDeviceAdded: card:" + card + " device:" + device);
-
-        MidiDeviceInfo deviceInfo;
-
-        synchronized (mDevices) {
-            int id = mNextDeviceId++;
-            Bundle properties = new Bundle();
-            properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER,
-                    usbDevice.getManufacturerName());
-            properties.putString(MidiDeviceInfo.PROPERTY_MODEL,
-                    usbDevice.getProductName());
-            properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
-                    usbDevice.getSerialNumber());
-            properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
-
-            // FIXME - multiple ports not supported yet
-            int inputPorts = 1;
-            int outputPorts = 1;
-
-            deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_USB, id, inputPorts, outputPorts,
-                    properties, card, device);
-            UsbMidiDevice midiDevice = new UsbMidiDevice(deviceInfo);
-            mDevices.put(id, midiDevice);
-            mUsbDevices.put(usbDevice, midiDevice);
-        }
-
-        synchronized (mClients) {
-            for (Client client : mClients.values()) {
-                client.deviceAdded(deviceInfo);
-            }
-        }
-    }
-
-    // called by UsbAudioManager to notify of removed USB MIDI devices
-    public void alsaDeviceRemoved(UsbDevice usbDevice) {
-        MidiDeviceInfo deviceInfo = null;
-
-        synchronized (mDevices) {
-            MidiDeviceBase device = mUsbDevices.remove(usbDevice);
-            if (device != null) {
-                device.close();
-                deviceInfo = device.getInfo();
-                mDevices.remove(deviceInfo.getId());
-            }
-        }
-
-        Log.d(TAG, "alsaDeviceRemoved: " + deviceInfo);
-
-        if (deviceInfo != null) {
-            synchronized (mClients) {
-                for (Client client : mClients.values()) {
-                    client.deviceRemoved(deviceInfo);
-                }
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/midi/UsbMidiDevice.java b/services/core/java/com/android/server/midi/UsbMidiDevice.java
deleted file mode 100644
index 3d42c67..0000000
--- a/services/core/java/com/android/server/midi/UsbMidiDevice.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.midi.MidiUtils;
-import android.os.Binder;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-// This is our subclass of MidiDeviceBase for communicating with USB MIDI devices
-// via the ALSA driver file system.
-class UsbMidiDevice extends MidiDeviceBase {
-    private static final String TAG = "UsbMidiDevice";
-
-    private FileInputStream mInputStream;
-    private FileOutputStream mOutputStream;
-    private final byte[] mBuffer = new byte[3];
-
-    public UsbMidiDevice(MidiDeviceInfo info) {
-        super(info);
-    }
-
-    public boolean open() {
-        if (mInputStream != null && mOutputStream != null) {
-            // already open
-            return true;
-        }
-
-        int card = mDeviceInfo.getAlsaCard();
-        int device = mDeviceInfo.getAlsaDevice();
-        if (card == -1 || device == -1) {
-            Log.e(TAG, "Not a USB device!");
-            return false;
-        }
-
-        // clear calling identity so we can access the driver file.
-        long identity = Binder.clearCallingIdentity();
-
-        File file = new File("/dev/snd/midiC" + card + "D" + device);
-        try {
-            mInputStream = new FileInputStream(file);
-            mOutputStream = new FileOutputStream(file);
-        } catch (Exception e) {
-            Log.e(TAG, "could not open " + file);
-            return false;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        return true;
-    }
-
-    void close() {
-        super.close();
-        try {
-            if (mInputStream != null) {
-                mInputStream.close();
-            }
-            if (mOutputStream != null) {
-                mOutputStream.close();
-            }
-        } catch (IOException e) {
-        }
-    }
-
-    // Reads a message from the ALSA driver.
-    // The driver may return multiple messages, so we have to read byte at a time.
-    int readMessage(byte[] buffer) throws IOException {
-        if (mInputStream.read(mBuffer, 0, 1) != 1) {
-            Log.e(TAG, "could not read command byte");
-            return -1;
-        }
-        int dataSize = MidiUtils.getMessageDataSize(mBuffer[0]);
-        if (dataSize < 0) {
-            return -1;
-        }
-        if (dataSize > 0) {
-            if (mInputStream.read(mBuffer, 1, dataSize) != dataSize) {
-                Log.e(TAG, "could not read command data");
-                return -1;
-            }
-        }
-        return MidiDevice.packMessage(mBuffer, 0, dataSize + 1, System.nanoTime(),
-            0, // FIXME - multiple ports not supported yet
-            buffer);
-    }
-
-    // writes a message to the ALSA driver
-    void writeMessage(byte[] buffer, int count) throws IOException {
-        int offset = MidiDevice.getMessageOffset(buffer, count);
-        int size = MidiDevice.getMessageSize(buffer, count);
-        // FIXME - multiple ports not supported yet
-        mOutputStream.write(buffer, offset, count);
-    }
-}
-
diff --git a/services/core/java/com/android/server/midi/VirtualMidiDevice.java b/services/core/java/com/android/server/midi/VirtualMidiDevice.java
deleted file mode 100644
index 5b39045..0000000
--- a/services/core/java/com/android/server/midi/VirtualMidiDevice.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions an
- * limitations under the License.
- */
-
-package com.android.server.midi;
-
-import android.midi.MidiDevice;
-import android.midi.MidiDeviceInfo;
-import android.os.ParcelFileDescriptor;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-// Our subclass of MidiDeviceBase to implement a virtual MIDI device
-class VirtualMidiDevice extends MidiDeviceBase {
-    private static final String TAG = "VirtualMidiDevice";
-
-    private ParcelFileDescriptor[] mFileDescriptors;
-    private FileInputStream mInputStream;
-    private FileOutputStream mOutputStream;
-
-    public VirtualMidiDevice(MidiDeviceInfo info) {
-        super(info);
-    }
-
-    public boolean open() {
-        if (mInputStream != null && mOutputStream != null) {
-            // already open
-            return true;
-        }
-
-        try {
-            mFileDescriptors = ParcelFileDescriptor.createSocketPair(
-                                                        OsConstants.SOCK_SEQPACKET);
-            FileDescriptor fd = mFileDescriptors[0].getFileDescriptor();
-            mInputStream = new FileInputStream(fd);
-            mOutputStream = new FileOutputStream(fd);
-            return true;
-        } catch (IOException e) {
-            Log.e(TAG, "failed to create ParcelFileDescriptor pair");
-            return false;
-        }
-    }
-
-    void close() {
-        super.close();
-        try {
-            if (mInputStream != null) {
-                mInputStream.close();
-            }
-            if (mOutputStream != null) {
-                mOutputStream.close();
-            }
-            if (mFileDescriptors != null && mFileDescriptors[0] != null) {
-                mFileDescriptors[0].close();
-                // file descriptor 1 is passed to client process
-            }
-        } catch (IOException e) {
-        }
-    }
-
-    MidiDevice getProxy() {
-        return new MidiDevice(mDeviceInfo, mFileDescriptors[1]);
-    }
-
-    int readMessage(byte[] buffer) throws IOException {
-        int ret = mInputStream.read(buffer);
-        // for now, throw away the timestamp
-        return ret - 8;
-    }
-
-    void writeMessage(byte[] buffer, int count) throws IOException {
-        mOutputStream.write(buffer, 0, count);
-    }
-}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index dc073ad..7b74e91 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -22,6 +22,7 @@
     $(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
     $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
+    $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
     $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
     $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp
new file mode 100644
index 0000000..94853b8
--- /dev/null
+++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "UsbMidiDeviceJNI"
+#define LOG_NDEBUG 0
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <asm/byteorder.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sound/asound.h>
+
+namespace android
+{
+
+static jclass sFileDescriptorClass;
+
+static jint
+android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */,
+        jint card, jint device)
+{
+    char    path[100];
+
+    snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
+    int fd = open(path, O_RDWR);
+    if (fd < 0) {
+        ALOGE("could not open %s", path);
+        return 0;
+    }
+
+    struct snd_rawmidi_info info;
+    memset(&info, 0, sizeof(info));
+    info.device = device;
+    int ret = ioctl(fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, &info);
+    close(fd);
+
+    if (ret < 0) {
+        ALOGE("SNDRV_CTL_IOCTL_RAWMIDI_INFO failed, errno: %d path: %s", errno, path);
+        return -1;
+    }
+
+    ALOGD("subdevices_count: %d", info.subdevices_count);
+    return info.subdevices_count;
+}
+
+static jobjectArray
+android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device,
+        jint subdevice_count)
+{
+    char    path[100];
+
+    snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
+
+    jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL);
+    if (!fds) {
+        return NULL;
+    }
+
+    // to support multiple subdevices we open the same file multiple times
+    for (int i = 0; i < subdevice_count; i++) {
+        int fd = open(path, O_RDWR);
+        if (fd < 0) {
+            ALOGE("open failed on %s for index %d", path, i);
+            return NULL;
+        }
+
+        jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
+        env->SetObjectArrayElement(fds, i, fileDescriptor);
+        env->DeleteLocalRef(fileDescriptor);
+    }
+
+    return fds;
+}
+
+static JNINativeMethod method_table[] = {
+    { "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count },
+    { "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open },
+};
+
+int register_android_server_UsbMidiDevice(JNIEnv *env)
+{
+    jclass clazz = env->FindClass("java/io/FileDescriptor");
+    if (clazz == NULL) {
+        ALOGE("Can't find java/io/FileDescriptor");
+        return -1;
+    }
+    sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);;
+
+    clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
+    if (clazz == NULL) {
+        ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
+        return -1;
+    }
+
+    return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
+            method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c65b3be..7db7414 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -32,6 +32,7 @@
 int register_android_server_SerialService(JNIEnv* env);
 int register_android_server_SystemServer(JNIEnv* env);
 int register_android_server_UsbDeviceManager(JNIEnv* env);
+int register_android_server_UsbMidiDevice(JNIEnv* env);
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_VibratorService(JNIEnv* env);
 int register_android_server_location_GpsLocationProvider(JNIEnv* env);
@@ -65,6 +66,7 @@
     register_android_server_LightsService(env);
     register_android_server_AlarmManagerService(env);
     register_android_server_UsbDeviceManager(env);
+    register_android_server_UsbMidiDevice(env);
     register_android_server_UsbHostManager(env);
     register_android_server_VibratorService(env);
     register_android_server_SystemServer(env);