Add support for flushing MIDI all scheduled MIDI events on a port

Change-Id: I39d7862540d4d4b9e2df1265f9dd253541adb4c2
diff --git a/api/current.txt b/api/current.txt
index d3ae427..90020d1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16847,6 +16847,7 @@
 
   public abstract class MidiReceiver {
     ctor public MidiReceiver();
+    method public void flush() throws java.io.IOException;
     method public int getMaxMessageSize();
     method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
     method public void send(byte[], int, int) throws java.io.IOException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 431809e..b13ecd3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -18122,6 +18122,7 @@
 
   public abstract class MidiReceiver {
     ctor public MidiReceiver();
+    method public void flush() throws java.io.IOException;
     method public int getMaxMessageSize();
     method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
     method public void send(byte[], int, int) throws java.io.IOException;
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 7b9a48c..506902f6 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.midi;
 
+import java.util.Iterator;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -28,7 +29,7 @@
     private static final long NANOS_PER_MILLI = 1000000;
 
     private final Object mLock = new Object();
-    private SortedMap<Long, FastEventQueue> mEventBuffer;
+    volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
     private FastEventQueue mEventPool = null;
     private int mMaxPoolSize = 200;
     private boolean mClosed;
@@ -68,6 +69,7 @@
             mEventsRemoved++;
             SchedulableEvent event = mFirst;
             mFirst = event.mNext;
+            event.mNext = null;
             return event;
         }
 
@@ -87,7 +89,7 @@
      */
     public static class SchedulableEvent {
         private long mTimestamp;
-        private SchedulableEvent mNext = null;
+        volatile private SchedulableEvent mNext = null;
 
         /**
          * @param timestamp
@@ -235,6 +237,11 @@
         return event;
     }
 
+    protected void flush() {
+        // Replace our event buffer with a fresh empty one
+        mEventBuffer = new TreeMap<Long, FastEventQueue>();
+    }
+
     public void close() {
         synchronized (mLock) {
             mClosed = true;
diff --git a/core/java/com/android/internal/midi/MidiDispatcher.java b/core/java/com/android/internal/midi/MidiDispatcher.java
index 377bc68..70e699a 100644
--- a/core/java/com/android/internal/midi/MidiDispatcher.java
+++ b/core/java/com/android/internal/midi/MidiDispatcher.java
@@ -83,4 +83,11 @@
             }
         }
     }
+
+    @Override
+    public void flush() throws IOException {
+       for (MidiReceiver receiver : mReceivers) {
+            receiver.flush();
+       }
+    }
 }
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 42d70f6..4dc5838 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -28,16 +28,9 @@
     // Maintain a pool of scheduled events to reduce memory allocation.
     // This pool increases performance by about 14%.
     private final static int POOL_EVENT_SIZE = 16;
-
-    private final MidiReceiver[] mReceivers;
+    private MidiReceiver mReceiver = new SchedulingReceiver();
 
     private class SchedulingReceiver extends MidiReceiver {
-        private final int mPortNumber;
-
-        public SchedulingReceiver(int portNumber) {
-            mPortNumber = portNumber;
-        }
-
         /**
          * Store these bytes in the EventScheduler to be delivered at the specified
          * time.
@@ -47,14 +40,17 @@
                 throws IOException {
             MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
             if (event != null) {
-                event.portNumber = mPortNumber;
                 add(event);
             }
         }
+
+        @Override
+        public void flush() {
+            MidiEventScheduler.this.flush();
+        }
     }
 
     public static class MidiEvent extends SchedulableEvent {
-        public int portNumber;
         public int count = 0;
         public byte[] data;
 
@@ -80,17 +76,6 @@
         }
     }
 
-    public MidiEventScheduler() {
-        this(0);
-    }
-
-    public MidiEventScheduler(int portCount) {
-        mReceivers = new MidiReceiver[portCount];
-        for (int i = 0; i < portCount; i++) {
-            mReceivers[i] = new SchedulingReceiver(i);
-        }
-    }
-
     /**
      * Create an event that contains the message.
      */
@@ -132,15 +117,7 @@
      * @return the MidiReceiver
      */
     public MidiReceiver getReceiver() {
-        return mReceivers[0];
-    }
-
-    /**
-     * This MidiReceiver will write date to the scheduling buffer.
-     * @return the MidiReceiver
-     */
-    public MidiReceiver getReceiver(int portNumber) {
-        return mReceivers[portNumber];
+        return mReceiver;
     }
 
 }
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 1d3b37a..ff16a57 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -83,7 +83,18 @@
             if (mOutputStream == null) {
                 throw new IOException("MidiInputPort is closed");
             }
-            int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
+            int length = MidiPortImpl.packData(msg, offset, count, timestamp, mBuffer);
+            mOutputStream.write(mBuffer, 0, length);
+        }
+    }
+
+    @Override
+    public void flush() throws IOException {
+        synchronized (mBuffer) {
+            if (mOutputStream == null) {
+                throw new IOException("MidiInputPort is closed");
+            }
+            int length = MidiPortImpl.packFlush(mBuffer);
             mOutputStream.write(mBuffer, 0, length);
         }
     }
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 0290a76..7491f3c 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -62,12 +62,24 @@
                         // FIXME - inform receivers here?
                     }
 
-                    int offset = MidiPortImpl.getMessageOffset(buffer, count);
-                    int size = MidiPortImpl.getMessageSize(buffer, count);
-                    long timestamp = MidiPortImpl.getMessageTimeStamp(buffer, count);
+                    int packetType = MidiPortImpl.getPacketType(buffer, count);
+                    switch (packetType) {
+                        case MidiPortImpl.PACKET_TYPE_DATA: {
+                            int offset = MidiPortImpl.getDataOffset(buffer, count);
+                            int size = MidiPortImpl.getDataSize(buffer, count);
+                            long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);
 
-                    // dispatch to all our receivers
-                    mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+                            // dispatch to all our receivers
+                            mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+                            break;
+                        }
+                        case MidiPortImpl.PACKET_TYPE_FLUSH:
+                            mDispatcher.flush();
+                            break;
+                        default:
+                            Log.e(TAG, "Unknown packet type " + packetType);
+                            break;
+                    }
                 }
             } catch (IOException e) {
                 // FIXME report I/O failure?
diff --git a/media/java/android/media/midi/MidiPortImpl.java b/media/java/android/media/midi/MidiPortImpl.java
index 5795045..16fc214 100644
--- a/media/java/android/media/midi/MidiPortImpl.java
+++ b/media/java/android/media/midi/MidiPortImpl.java
@@ -24,6 +24,16 @@
     private static final String TAG = "MidiPort";
 
     /**
+     * Packet type for data packet
+     */
+    public static final int PACKET_TYPE_DATA = 1;
+
+    /**
+     * Packet type for flush packet
+     */
+    public static final int PACKET_TYPE_FLUSH = 2;
+
+    /**
      * Maximum size of a packet that can pass through our ParcelFileDescriptor.
      */
     public static final int MAX_PACKET_SIZE = 1024;
@@ -34,12 +44,17 @@
     private static final int TIMESTAMP_SIZE = 8;
 
     /**
-     * Maximum amount of MIDI data that can be included in a packet
+     * Data packet overhead is timestamp size plus packet type byte
      */
-    public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+    private static final int DATA_PACKET_OVERHEAD = TIMESTAMP_SIZE + 1;
 
     /**
-     * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
+     * Maximum amount of MIDI data that can be included in a packet
+     */
+    public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - DATA_PACKET_OVERHEAD;
+
+    /**
+     * Utility function for packing MIDI data to be sent through our ParcelFileDescriptor
      *
      * message byte array contains variable length MIDI message.
      * messageSize is size of variable length MIDI message
@@ -47,46 +62,65 @@
      * dest is buffer to pack into
      * returns size of packed message
      */
-    public static int packMessage(byte[] message, int offset, int size, long timestamp,
+    public static int packData(byte[] message, int offset, int size, long timestamp,
             byte[] dest) {
-        if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) {
-            size = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+        if (size  > MAX_PACKET_DATA_SIZE) {
+            size = MAX_PACKET_DATA_SIZE;
         }
-        // message data goes first
-        System.arraycopy(message, offset, dest, 0, size);
+        int length = 0;
+        // packet type goes first
+        dest[length++] = PACKET_TYPE_DATA;
+        // data goes next
+        System.arraycopy(message, offset, dest, length, size);
+        length += size;
 
         // followed by timestamp
         for (int i = 0; i < TIMESTAMP_SIZE; i++) {
-            dest[size++] = (byte)timestamp;
+            dest[length++] = (byte)timestamp;
             timestamp >>= 8;
         }
 
-        return size;
+        return length;
     }
 
     /**
-     * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+     * Utility function for packing a flush command to be sent through our ParcelFileDescriptor
+     */
+    public static int packFlush(byte[] dest) {
+        dest[0] = PACKET_TYPE_FLUSH;
+        return 1;
+    }
+
+    /**
+     * Returns the packet type (PACKET_TYPE_DATA or PACKET_TYPE_FLUSH)
+     */
+    public static int getPacketType(byte[] buffer, int bufferLength) {
+        return buffer[0];
+    }
+
+    /**
+     * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
      * returns the offset of the MIDI message in packed buffer
      */
-    public static int getMessageOffset(byte[] buffer, int bufferLength) {
-        // message is at the beginning
-        return 0;
+    public static int getDataOffset(byte[] buffer, int bufferLength) {
+        // data follows packet type byte
+        return 1;
     }
 
     /**
-     * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+     * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
      * returns size of MIDI data in packed buffer
      */
-    public static int getMessageSize(byte[] buffer, int bufferLength) {
+    public static int getDataSize(byte[] buffer, int bufferLength) {
         // message length is total buffer length minus size of the timestamp
-        return bufferLength - TIMESTAMP_SIZE;
+        return bufferLength - DATA_PACKET_OVERHEAD;
     }
 
     /**
-     * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+     * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
      * unpacks timestamp from packed buffer
      */
-    public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
+    public static long getPacketTimestamp(byte[] buffer, int bufferLength) {
         // timestamp is at end of the packet
         int offset = bufferLength;
         long timestamp = 0;
diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java
index 6f4c266..d069075 100644
--- a/media/java/android/media/midi/MidiReceiver.java
+++ b/media/java/android/media/midi/MidiReceiver.java
@@ -42,6 +42,13 @@
             throws IOException;
 
     /**
+     * Instructs the receiver to discard all pending events.
+     * @throws IOException
+     */
+    public void flush() throws IOException {
+    }
+
+    /**
      * Returns the maximum size of a message this receiver can receive.
      * Defaults to {@link java.lang.Integer#MAX_VALUE} unless overridden.
      * @return maximum message size
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 6ece888..671cf01 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -45,7 +45,8 @@
 
     private MidiDeviceServer mServer;
 
-    private final MidiEventScheduler mEventScheduler;
+    // event schedulers for each output port
+    private final MidiEventScheduler[] mEventSchedulers;
 
     private static final int BUFFER_SIZE = 512;
 
@@ -99,10 +100,11 @@
         }
 
         mOutputStreams = new FileOutputStream[outputCount];
+        mEventSchedulers = new MidiEventScheduler[outputCount];
         for (int i = 0; i < outputCount; i++) {
             mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
+            mEventSchedulers[i] = new MidiEventScheduler();
         }
-        mEventScheduler = new MidiEventScheduler(inputCount);
     }
 
     private boolean register(Context context, Bundle properties) {
@@ -116,7 +118,7 @@
         int outputCount = mOutputStreams.length;
         MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
         for (int port = 0; port < inputCount; port++) {
-            inputPortReceivers[port] = mEventScheduler.getReceiver(port);
+            inputPortReceivers[port] = mEventSchedulers[port].getReceiver();
         }
 
         mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
@@ -126,7 +128,7 @@
         }
         final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
 
-        // Create input thread
+        // Create input thread which will read from all input ports
         new Thread("UsbMidiDevice input thread") {
             @Override
             public void run() {
@@ -161,38 +163,46 @@
             }
         }.start();
 
-        // Create output thread
-        new Thread("UsbMidiDevice output thread") {
-            @Override
-            public void run() {
-                while (true) {
-                    MidiEvent event;
-                    try {
-                        event = (MidiEvent)mEventScheduler.waitNextEvent();
-                    } catch (InterruptedException e) {
-                        // try again
-                        continue;
+        // Create output thread for each output port
+        for (int port = 0; port < outputCount; port++) {
+            final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
+            final FileOutputStream outputStreamF = mOutputStreams[port];
+            final int portF = port;
+
+            new Thread("UsbMidiDevice output thread " + port) {
+                @Override
+                public void run() {
+                    while (true) {
+                        MidiEvent event;
+                        try {
+                            event = (MidiEvent)eventSchedulerF.waitNextEvent();
+                        } catch (InterruptedException e) {
+                            // try again
+                            continue;
+                        }
+                        if (event == null) {
+                            break;
+                        }
+                        try {
+                            outputStreamF.write(event.data, 0, event.count);
+                        } catch (IOException e) {
+                            Log.e(TAG, "write failed for port " + portF);
+                        }
+                        eventSchedulerF.addEventToPool(event);
                     }
-                    if (event == null) {
-                        break;
-                    }
-                    try {
-                        mOutputStreams[event.portNumber].write(event.data, 0, event.count);
-                    } catch (IOException e) {
-                        Log.e(TAG, "write failed for port " + event.portNumber);
-                    }
-                    mEventScheduler.addEventToPool(event);
+                    Log.d(TAG, "output thread exit");
                 }
-                Log.d(TAG, "output thread exit");
-            }
-        }.start();
+            }.start();
+        }
 
         return true;
     }
 
     @Override
     public void close() throws IOException {
-        mEventScheduler.close();
+        for (int i = 0; i < mEventSchedulers.length; i++) {
+            mEventSchedulers[i].close();
+        }
 
         if (mServer != null) {
             mServer.close();