MIDI Manager work in progress

Still to do:

Add MidiInputPort and MidiOutputPort classes
Schedule sending MIDI events in the future
Security/permissions
Reconsider interface for virtual devices
Look into performance optimizations

Change-Id: I9b7d63b196996a04be0a830efa913043da1328a8
diff --git a/Android.mk b/Android.mk
index ec0c235..2dc210f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -172,6 +172,8 @@
 	core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
 	core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \
 	core/java/android/hardware/usb/IUsbManager.aidl \
+	core/java/android/midi/IMidiListener.aidl \
+	core/java/android/midi/IMidiManager.aidl \
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/IEthernetManager.aidl \
 	core/java/android/net/IEthernetServiceListener.aidl \
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 12fee2d..eadf5e9 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -80,6 +80,8 @@
 import android.media.session.MediaSessionManager;
 import android.media.tv.ITvInputManager;
 import android.media.tv.TvInputManager;
+import android.midi.IMidiManager;
+import android.midi.MidiManager;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.EthernetManager;
@@ -777,6 +779,12 @@
                 IBinder b = ServiceManager.getService(APPWIDGET_SERVICE);
                 return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
             }});
+
+        registerService(MIDI_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
+                    IBinder b = ServiceManager.getService(MIDI_SERVICE);
+                    return new MidiManager(ctx, IMidiManager.Stub.asInterface(b));
+                }});
     }
 
     static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index e6bb09f..b0bfdb6 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2142,6 +2142,7 @@
             MEDIA_SESSION_SERVICE,
             BATTERY_SERVICE,
             JOB_SCHEDULER_SERVICE,
+            MIDI_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -2915,6 +2916,15 @@
     public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.midi.MidiManager} for accessing the MIDI service.
+     *
+     * @see #getSystemService
+     * @hide
+     */
+    public static final String MIDI_SERVICE = "midi";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/midi/IMidiListener.aidl b/core/java/android/midi/IMidiListener.aidl
new file mode 100644
index 0000000..b650593
--- /dev/null
+++ b/core/java/android/midi/IMidiListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.midi.MidiDeviceInfo;
+
+/** @hide */
+oneway interface IMidiListener
+{
+    void onDeviceAdded(in MidiDeviceInfo device);
+    void onDeviceRemoved(in MidiDeviceInfo device);
+}
diff --git a/core/java/android/midi/IMidiManager.aidl b/core/java/android/midi/IMidiManager.aidl
new file mode 100644
index 0000000..06b675b
--- /dev/null
+++ b/core/java/android/midi/IMidiManager.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.hardware.usb.UsbDevice;
+import android.midi.IMidiListener;
+import android.midi.MidiDevice;
+import android.midi.MidiDeviceInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+interface IMidiManager
+{
+    MidiDeviceInfo[] getDeviceList();
+
+    // for device creation & removal notifications
+    void registerListener(IBinder token, in IMidiListener listener);
+    void unregisterListener(IBinder token, in IMidiListener listener);
+
+    // for communicating with MIDI devices
+    ParcelFileDescriptor openDevice(IBinder token, in MidiDeviceInfo device);
+
+    // for implementing virtual MIDI devices
+    MidiDevice registerVirtualDevice(IBinder token, in Bundle properties);
+    void unregisterVirtualDevice(IBinder token, in MidiDeviceInfo device);
+
+    // for use by UsbAudioManager
+    void alsaDeviceAdded(int card, int device, in UsbDevice usbDevice);
+    void alsaDeviceRemoved(in UsbDevice usbDevice);
+}
diff --git a/core/java/android/midi/MidiDevice.aidl b/core/java/android/midi/MidiDevice.aidl
new file mode 100644
index 0000000..11bb497
--- /dev/null
+++ b/core/java/android/midi/MidiDevice.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+parcelable MidiDevice;
diff --git a/core/java/android/midi/MidiDevice.java b/core/java/android/midi/MidiDevice.java
new file mode 100644
index 0000000..8954d2b
--- /dev/null
+++ b/core/java/android/midi/MidiDevice.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is used for sending and receiving data to and from an midi device
+ * Instances of this class are created by {@link MidiManager#openDevice}.
+ * This class can also be used to provide the implementation for a virtual device.
+ *
+ * This class implements Parcelable so it can be returned from MidiService when creating
+ * virtual MIDI devices.
+ *
+ * @hide
+ */
+public final class MidiDevice implements Parcelable {
+    private static final String TAG = "MidiDevice";
+
+    private final MidiDeviceInfo mDeviceInfo;
+    private ParcelFileDescriptor mParcelFileDescriptor;
+    private FileInputStream mInputStream;
+    private FileOutputStream mOutputStream;
+    private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
+
+    /**
+     * Minimum size of packed message as sent through our ParcelFileDescriptor
+     * 8 bytes for timestamp and 1 to 3 bytes for message
+     * @hide
+     */
+    public static final int MIN_PACKED_MESSAGE_SIZE = 9;
+
+    /**
+     * Maximum size of packed message as sent through our ParcelFileDescriptor
+     * 8 bytes for timestamp and 1 to 3 bytes for message
+     * @hide
+     */
+    public static final int MAX_PACKED_MESSAGE_SIZE = 11;
+
+    // This thread reads MIDI events from a socket and distributes them to the list of
+    // MidiReceivers attached to this device.
+    private final Thread mThread = new Thread() {
+        @Override
+        public void run() {
+            byte[] buffer = new byte[MAX_PACKED_MESSAGE_SIZE];
+            ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
+
+            try {
+                while (true) {
+                    // read next event
+                    int count = mInputStream.read(buffer);
+                    if (count < MIN_PACKED_MESSAGE_SIZE || count > MAX_PACKED_MESSAGE_SIZE) {
+                        Log.e(TAG, "Number of bytes read out of range: " + count);
+                        break;
+                    }
+
+                    int offset = getMessageOffset(buffer, count);
+                    int size = getMessageSize(buffer, count);
+                    long timestamp = getMessageTimeStamp(buffer, count);
+
+                    synchronized (mReceivers) {
+                        for (int i = 0; i < mReceivers.size(); i++) {
+                            MidiReceiver receiver = mReceivers.get(i);
+                            try {
+                                mReceivers.get(i).onPost(buffer, offset, size, timestamp);
+                            } catch (IOException e) {
+                                Log.e(TAG, "post failed");
+                                deadReceivers.add(receiver);
+                            }
+                        }
+                        // remove any receivers that failed
+                        if (deadReceivers.size() > 0) {
+                            for (MidiReceiver receiver: deadReceivers) {
+                                mReceivers.remove(receiver);
+                            }
+                            deadReceivers.clear();
+                        }
+                        // exit if we have no receivers left
+                        if (mReceivers.size() == 0) {
+                            break;
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "read failed");
+            }
+        }
+    };
+
+    // This is the receiver that clients use for sending events to this device.
+    private final MidiReceiver mReceiver = new MidiReceiver() {
+        private final byte[] mBuffer = new byte[MAX_PACKED_MESSAGE_SIZE];
+        public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
+            synchronized (mBuffer) {
+                int length = packMessage(msg, offset, count, timestamp, mBuffer);
+                mOutputStream.write(mBuffer, 0, length);
+            }
+        }
+    };
+
+    // Our MidiSender object, to which clients can attach MidiReceivers.
+    private final MidiSender mSender = new MidiSender() {
+        public void connect(MidiReceiver receiver) {
+            synchronized (mReceivers) {
+                if (mReceivers.size() == 0) {
+                    mThread.start();
+                }
+                mReceivers.add(receiver);
+            }
+        }
+
+        public void disconnect(MidiReceiver receiver) {
+            synchronized (mReceivers) {
+                mReceivers.remove(receiver);
+                if (mReceivers.size() == 0) {
+                    // ???
+                }
+            }
+        }
+    };
+
+   /**
+     * MidiDevice should only be instantiated by MidiManager or MidiService
+     * @hide
+     */
+    public MidiDevice(MidiDeviceInfo deviceInfo, ParcelFileDescriptor pfd) {
+        mDeviceInfo = deviceInfo;
+        mParcelFileDescriptor = pfd;
+    }
+
+    public boolean open() {
+        FileDescriptor fd = mParcelFileDescriptor.getFileDescriptor();
+        try {
+            mInputStream = new FileInputStream(fd);
+        } catch (Exception e) {
+            Log.e(TAG, "could not create mInputStream", e);
+            return false;
+        }
+
+        try {
+            mOutputStream = new FileOutputStream(fd);
+        } catch (Exception e) {
+            Log.e(TAG, "could not create mOutputStream", e);
+            return false;
+        }
+        return true;
+    }
+
+    void close() {
+        try {
+            if (mInputStream != null) {
+                mInputStream.close();
+            }
+            if (mOutputStream != null) {
+                mOutputStream.close();
+            }
+            mParcelFileDescriptor.close();
+        } catch (IOException e) {
+        }
+    }
+
+    // returns our MidiDeviceInfo object, which describes this device
+    public MidiDeviceInfo getInfo() {
+        return mDeviceInfo;
+    }
+
+    // returns our MidiReceiver, which clients can use for sending events to this device.
+    public MidiReceiver getReceiver() {
+        return mReceiver;
+    }
+
+    // Returns our MidiSender object, to which clients can attach MidiReceivers.
+    public MidiSender getSender() {
+        return mSender;
+    }
+
+    @Override
+    public String toString() {
+        return ("MidiDevice: " + mDeviceInfo.toString() + " fd: " + mParcelFileDescriptor);
+    }
+
+    public static final Parcelable.Creator<MidiDevice> CREATOR =
+        new Parcelable.Creator<MidiDevice>() {
+        public MidiDevice createFromParcel(Parcel in) {
+            MidiDeviceInfo deviceInfo = (MidiDeviceInfo)in.readParcelable(null);
+            ParcelFileDescriptor pfd = (ParcelFileDescriptor)in.readParcelable(null);
+            return new MidiDevice(deviceInfo, pfd);
+        }
+
+        public MidiDevice[] newArray(int size) {
+            return new MidiDevice[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeParcelable(mDeviceInfo, flags);
+        parcel.writeParcelable(mParcelFileDescriptor, flags);
+   }
+
+    /**
+     * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
+     *
+     * message byte array contains variable length MIDI message.
+     * messageSize is size of variable length MIDI message
+     * timestamp is message timestamp to pack
+     * dest is buffer to pack into
+     * returns size of packed message
+     *
+     * @hide
+     */
+    public static int packMessage(byte[] message, int offset, int size, long timestamp,
+            byte[] dest) {
+        // pack variable length message first
+        System.arraycopy(message, offset, dest, 0, size);
+        int destOffset = size;
+        // timestamp takes 8 bytes
+        for (int i = 0; i < 8; i++) {
+            dest[destOffset++] = (byte)timestamp;
+            timestamp >>= 8;
+        }
+        return destOffset;
+    }
+
+    /**
+     * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
+     * returns the offet of of MIDI message in packed buffer
+     *
+     * @hide
+     */
+    public static int getMessageOffset(byte[] buffer, int bufferLength) {
+        // message is at start of buffer
+        return 0;
+    }
+
+    /**
+     * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
+     * returns size of MIDI message in packed buffer
+     *
+     * @hide
+     */
+    public static int getMessageSize(byte[] buffer, int bufferLength) {
+        // message length is total buffer length minus size of the timestamp
+        return bufferLength - 8;
+    }
+
+    /**
+     * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
+     * unpacks timestamp from packed buffer
+     *
+     * @hide
+     */
+    public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
+        long timestamp = 0;
+
+        // timestamp follows variable length message data
+        int dataLength = getMessageSize(buffer, bufferLength);
+        for (int i = dataLength + 7; i >= dataLength; i--) {
+            // why can't Java deal with unsigned ints?
+            int b = buffer[i];
+            if (b < 0) b += 256;
+            timestamp = (timestamp << 8) | b;
+        }
+        return timestamp;
+     }
+}
diff --git a/core/java/android/midi/MidiDeviceInfo.aidl b/core/java/android/midi/MidiDeviceInfo.aidl
new file mode 100644
index 0000000..59be059
--- /dev/null
+++ b/core/java/android/midi/MidiDeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+parcelable MidiDeviceInfo;
diff --git a/core/java/android/midi/MidiDeviceInfo.java b/core/java/android/midi/MidiDeviceInfo.java
new file mode 100644
index 0000000..def6878c
--- /dev/null
+++ b/core/java/android/midi/MidiDeviceInfo.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains information to describe a MIDI device.
+ * For now we only have information that can be retrieved easily for USB devices,
+ * but we will probably expand this in the future.
+ *
+ * This class is just an immutable object to encapsulate the MIDI device description.
+ * Use the MidiDevice class to actually communicate with devices.
+ *
+ * @hide
+ */
+public class MidiDeviceInfo implements Parcelable {
+
+    private static final String TAG = "MidiDeviceInfo";
+
+    public static final int TYPE_USB = 1;
+    public static final int TYPE_VIRTUAL = 2;
+
+    private final int mType;    // USB or virtual
+    private final int mId;  // unique ID generated by MidiService
+    private final Bundle mProperties;
+
+    // used for USB devices only
+    private final int mAlsaCard;
+    private final int mAlsaDevice;
+
+    /**
+     * Bundle key for the device's manufacturer name property.
+     * Used with the {@link android.os.Bundle} returned by {@link #getProperties}.
+     * Matches the USB device manufacturer name string for USB MIDI devices.
+     */
+    public static final String PROPERTY_MANUFACTURER = "manufacturer";
+
+    /**
+     * Bundle key for the device's model name property.
+     * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+     * Matches the USB device product name string for USB MIDI devices.
+     */
+    public static final String PROPERTY_MODEL = "model";
+
+    /**
+     * Bundle key for the device's serial number property.
+     * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+     * Matches the USB device serial number for USB MIDI devices.
+     */
+    public static final String PROPERTY_SERIAL_NUMBER = "serial_number";
+
+    /**
+     * Bundle key for the device's {@link android.hardware.usb.UsbDevice}.
+     * Only set for USB MIDI devices.
+     * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+     */
+    public static final String PROPERTY_USB_DEVICE = "usb_device";
+
+    /**
+     * MidiDeviceInfo should only be instantiated by MidiService implementation
+     * @hide
+     */
+    public MidiDeviceInfo(int type, int id, Bundle properties) {
+        mType = type;
+        mId = id;
+        mProperties = properties;
+        mAlsaCard = -1;
+        mAlsaDevice = -1;
+    }
+
+    /**
+     * MidiDeviceInfo should only be instantiated by MidiService implementation
+     * @hide
+     */
+    public MidiDeviceInfo(int type, int id, Bundle properties,
+            int alsaCard, int alsaDevice) {
+        mType = type;
+        mId = id;
+        mProperties = properties;
+        mAlsaCard = alsaCard;
+        mAlsaDevice = alsaDevice;
+    }
+
+    /**
+     * Returns the type of the device.
+     *
+     * @return the device's type
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the ID of the device.
+     * This ID is generated by the MIDI service and is not persistent across device unplugs.
+     *
+     * @return the device's ID
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the {@link android.os.Bundle} containing the device's properties.
+     *
+     * @return the device's properties
+     */
+    public Bundle getProperties() {
+        return mProperties;
+    }
+
+    /**
+     * @hide
+     */
+    public int getAlsaCard() {
+        return mAlsaCard;
+    }
+
+    /**
+     * @hide
+     */
+    public int getAlsaDevice() {
+        return mAlsaDevice;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof MidiDeviceInfo) {
+            return (((MidiDeviceInfo)o).mId == mId);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return mId;
+    }
+
+    @Override
+    public String toString() {
+        return ("MidiDeviceInfo[mType=" + mType +
+                ",mId=" + mId +
+                ",mProperties=" + mProperties +
+                ",mAlsaCard=" + mAlsaCard +
+                ",mAlsaDevice=" + mAlsaDevice);
+    }
+
+    public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
+        new Parcelable.Creator<MidiDeviceInfo>() {
+        public MidiDeviceInfo createFromParcel(Parcel in) {
+            int type = in.readInt();
+            int id = in.readInt();
+            Bundle properties = in.readBundle();
+            int card = in.readInt();
+            int device = in.readInt();
+            return new MidiDeviceInfo(type, id, properties, card, device);
+        }
+
+        public MidiDeviceInfo[] newArray(int size) {
+            return new MidiDeviceInfo[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mType);
+        parcel.writeInt(mId);
+        parcel.writeBundle(mProperties);
+        parcel.writeInt(mAlsaCard);
+        parcel.writeInt(mAlsaDevice);
+   }
+}
diff --git a/core/java/android/midi/MidiManager.java b/core/java/android/midi/MidiManager.java
new file mode 100644
index 0000000..ec869b7
--- /dev/null
+++ b/core/java/android/midi/MidiManager.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * This class is the public application interface to the MIDI service.
+ *
+ * <p>You can obtain an instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
+ *
+ * {@samplecode
+ * MidiManager manager = (MidiManager) getSystemService(Context.MIDI_SERVICE);}
+ * @hide
+ */
+public class MidiManager {
+    private static final String TAG = "MidiManager";
+
+    private final Context mContext;
+    private final IMidiManager mService;
+    private final IBinder mToken = new Binder();
+
+    private HashMap<DeviceCallback,DeviceListener> mDeviceListeners =
+        new HashMap<DeviceCallback,DeviceListener>();
+
+    // Binder stub for receiving device notifications from MidiService
+    private class DeviceListener extends IMidiListener.Stub {
+        private DeviceCallback mCallback;
+
+        public DeviceListener(DeviceCallback callback) {
+            mCallback = callback;
+        }
+
+        public void onDeviceAdded(MidiDeviceInfo device) {
+            mCallback.onDeviceAdded(device);
+        }
+
+        public void onDeviceRemoved(MidiDeviceInfo device) {
+            mCallback.onDeviceRemoved(device);
+        }
+    }
+
+    // Callback interface clients to receive Device added and removed notifications
+    public interface DeviceCallback {
+        void onDeviceAdded(MidiDeviceInfo device);
+        void onDeviceRemoved(MidiDeviceInfo device);
+    }
+
+    /**
+     * @hide
+     */
+    public MidiManager(Context context, IMidiManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    // Used by clients to register for Device added and removed notifications
+    public void registerDeviceCallback(DeviceCallback callback) {
+        DeviceListener deviceListener = new DeviceListener(callback);
+        try {
+            mService.registerListener(mToken, deviceListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in registerDeviceListener");
+            return;
+        }
+        mDeviceListeners.put(callback, deviceListener);
+    }
+
+    // Used by clients to unregister for device added and removed notifications
+    public void unregisterDeviceCallback(DeviceCallback callback) {
+        DeviceListener deviceListener = mDeviceListeners.remove(callback);
+        if (deviceListener != null) {
+            try {
+                mService.unregisterListener(mToken, deviceListener);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in unregisterDeviceListener");
+            }
+        }
+    }
+
+    public MidiDeviceInfo[] getDeviceList() {
+        try {
+           return mService.getDeviceList();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getDeviceList");
+            return new MidiDeviceInfo[0];
+        }
+    }
+
+    // Use this if you want to communicate with a MIDI device.
+    public MidiDevice openDevice(MidiDeviceInfo deviceInfo) {
+        try {
+            ParcelFileDescriptor pfd = mService.openDevice(mToken, deviceInfo);
+            if (pfd == null) {
+                Log.e(TAG, "could not open device " + deviceInfo);
+                return null;
+            }
+            MidiDevice device = new MidiDevice(deviceInfo, pfd);
+            if (device.open()) {
+                Log.d(TAG, "openDevice returning " + device);
+                return device;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in openDevice");
+        }
+        return null;
+    }
+
+    // Use this if you want to register and implement a virtual device.
+    // The MidiDevice returned by this method is the proxy you use to implement the device.
+    public MidiDevice createVirtualDevice(Bundle properties) {
+        try {
+            MidiDevice device = mService.registerVirtualDevice(mToken, properties);
+            if (device != null && !device.open()) {
+                device = null;
+            }
+            return device;
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in createVirtualDevice");
+            return null;
+        }
+    }
+
+    public void closeVirtualDevice(MidiDevice device) {
+        try {
+            device.close();
+            mService.unregisterVirtualDevice(mToken, device.getInfo());
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in unregisterVirtualDevice");
+        }
+    }
+}
diff --git a/core/java/android/midi/MidiReceiver.java b/core/java/android/midi/MidiReceiver.java
new file mode 100644
index 0000000..1101105
--- /dev/null
+++ b/core/java/android/midi/MidiReceiver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import java.io.IOException;
+
+/**
+ * Interface for receiving events from a MIDI device.
+ *
+ * @hide
+ */
+public interface MidiReceiver {
+    // NOTE: the msg array is only valid within the context of this call.
+    // the byte array may get reused by the MIDI device for the next message.
+    public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException;
+}
diff --git a/core/java/android/midi/MidiSender.java b/core/java/android/midi/MidiSender.java
new file mode 100644
index 0000000..cba7079
--- /dev/null
+++ b/core/java/android/midi/MidiSender.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+/**
+ * Interface provided by a device to allow attaching
+ * MidiReceivers to a MIDI device.
+ *
+ * @hide
+ */
+public interface MidiSender {
+    public void connect(MidiReceiver receiver);
+    public void disconnect(MidiReceiver receiver);
+}
diff --git a/core/java/android/midi/MidiUtils.java b/core/java/android/midi/MidiUtils.java
new file mode 100644
index 0000000..f80e83a
--- /dev/null
+++ b/core/java/android/midi/MidiUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.util.Log;
+
+/**
+ * Class containing miscellaneous MIDI utilities.
+ *
+ * @hide
+ */
+public final class MidiUtils {
+    private static final String TAG = "MidiUtils";
+
+    /**
+     * Returns data size of a MIDI message based on the message's command byte
+     * @param b the message command byte
+     * @return the message's data length
+     */
+    public static int getMessageDataSize(byte b) {
+        switch (b & 0xF0) {
+            case 0x80:
+            case 0x90:
+            case 0xA0:
+            case 0xB0:
+            case 0xE0:
+                return 2;
+            case 0xC0:
+            case 0xD0:
+                return 1;
+            case 0xF0:
+                switch (b & 0x0F) {
+                    case 0x00:
+                        Log.e(TAG, "System Exclusive not supported yet");
+                        return -1;
+                    case 0x01:
+                    case 0x03:
+                        return 1;
+                    case 0x02:
+                        return 2;
+                    default:
+                        return 0;
+                }
+            default:
+                Log.e(TAG, "unknown MIDI command " + b);
+                return -1;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/midi/MidiDeviceBase.java b/services/core/java/com/android/server/midi/MidiDeviceBase.java
new file mode 100644
index 0000000..4289584
--- /dev/null
+++ b/services/core/java/com/android/server/midi/MidiDeviceBase.java
@@ -0,0 +1,170 @@
+/*
+ * 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
new file mode 100644
index 0000000..ff8dda0
--- /dev/null
+++ b/services/core/java/com/android/server/midi/MidiService.java
@@ -0,0 +1,277 @@
+/*
+ * 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, 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,
+                    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);
+
+            deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_USB, id, 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
new file mode 100644
index 0000000..1bc91f05
--- /dev/null
+++ b/services/core/java/com/android/server/midi/UsbMidiDevice.java
@@ -0,0 +1,113 @@
+/*
+ * 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(), 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);
+        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
new file mode 100644
index 0000000..5b39045
--- /dev/null
+++ b/services/core/java/com/android/server/midi/VirtualMidiDevice.java
@@ -0,0 +1,91 @@
+/*
+ * 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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0ccb25c..db60866 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -74,6 +74,7 @@
 import com.android.server.media.MediaRouterService;
 import com.android.server.media.MediaSessionService;
 import com.android.server.media.projection.MediaProjectionManagerService;
+import com.android.server.midi.MidiService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
 import com.android.server.notification.NotificationManagerService;
@@ -410,6 +411,7 @@
         AudioService audioService = null;
         MmsServiceBroker mmsService = null;
         EntropyMixer entropyMixer = null;
+        MidiService midiService = null;
 
         boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
         boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false);
@@ -521,6 +523,7 @@
         LockSettingsService lockSettings = null;
         AssetAtlasService atlas = null;
         MediaRouterService mediaRouter = null;
+        MidiService midi = null;
 
         // Bring up services needed for UI.
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
@@ -827,6 +830,16 @@
                 }
             }
 
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "MIDI Service");
+                    ServiceManager.addService(Context.MIDI_SERVICE,
+                            new MidiService(context));
+                } catch (Throwable e) {
+                    reportWtf("starting MIDI Service", e);
+                }
+            }
+
             mSystemServiceManager.startService(TwilightService.class);
 
             mSystemServiceManager.startService(UiModeManagerService.class);
diff --git a/services/usb/java/com/android/server/usb/UsbAudioManager.java b/services/usb/java/com/android/server/usb/UsbAudioManager.java
index 95eb92b..4142032 100644
--- a/services/usb/java/com/android/server/usb/UsbAudioManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAudioManager.java
@@ -24,7 +24,11 @@
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbInterface;
 import android.media.AudioManager;
+import android.midi.IMidiManager;
 import android.os.FileObserver;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Slog;
@@ -44,6 +48,7 @@
     private static final String ALSA_DIRECTORY = "/dev/snd/";
 
     private final Context mContext;
+    private IMidiManager mMidiManager;
 
     private final class AudioDevice {
         public int mCard;
@@ -132,6 +137,8 @@
     }
 
     public void systemReady() {
+        final IBinder b = ServiceManager.getService(Context.MIDI_SERVICE);
+        mMidiManager = IMidiManager.Stub.asInterface(b);
         mAlsaObserver.startWatching();
 
         // add existing alsa devices
@@ -241,6 +248,7 @@
 
         // Is there an audio interface in there?
         boolean isAudioDevice = false;
+        AlsaDevice midiDevice = null;
 
         // FIXME - handle multiple configurations?
         int interfaceCount = usbDevice.getInterfaceCount();
@@ -289,6 +297,11 @@
             return;
         }
 
+        // MIDI device file needed/present?
+        if (hasMidi) {
+            midiDevice = waitForAlsaDevice(card, device, AlsaDevice.TYPE_MIDI);
+        }
+
         if (DEBUG) {
             Slog.d(TAG,
                     "usb: hasPlayback:" + hasPlayback +
@@ -299,6 +312,14 @@
         AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi);
         mAudioDevices.put(usbDevice, audioDevice);
         sendDeviceNotification(audioDevice, true);
+
+        if (midiDevice != null && mMidiManager != null) {
+            try {
+                mMidiManager.alsaDeviceAdded(midiDevice.mCard, midiDevice.mDevice, usbDevice);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "MIDI Manager dead", e);
+            }
+        }
     }
 
     /* package */ void deviceRemoved(UsbDevice device) {
@@ -311,6 +332,13 @@
             if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) {
                 sendDeviceNotification(audioDevice, false);
             }
+            if (audioDevice.mHasMIDI) {
+                try {
+                    mMidiManager.alsaDeviceRemoved(device);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "MIDI Manager dead", e);
+                }
+            }
         }
     }