Merge "Editor: Fix crash when selecting text on a non-editable TextView"
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index 56da940..0173079 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -118,13 +118,14 @@
             FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
             if (fraction < nextKeyframe.getFraction()) {
                 final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
-                if (interpolator != null) {
-                    fraction = interpolator.getInterpolation(fraction);
-                }
                 float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                     (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                 float prevValue = prevKeyframe.getFloatValue();
                 float nextValue = nextKeyframe.getFloatValue();
+                // Apply interpolator on the proportional duration.
+                if (interpolator != null) {
+                    intervalFraction = interpolator.getInterpolation(intervalFraction);
+                }
                 return mEvaluator == null ?
                         prevValue + intervalFraction * (nextValue - prevValue) :
                         ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index 12a4bf9..73f9af1 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -117,13 +117,14 @@
             IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
             if (fraction < nextKeyframe.getFraction()) {
                 final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
-                if (interpolator != null) {
-                    fraction = interpolator.getInterpolation(fraction);
-                }
                 float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                     (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                 int prevValue = prevKeyframe.getIntValue();
                 int nextValue = nextKeyframe.getIntValue();
+                // Apply interpolator on the proportional duration.
+                if (interpolator != null) {
+                    intervalFraction = interpolator.getInterpolation(intervalFraction);
+                }
                 return mEvaluator == null ?
                         prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                         ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index c80e162..32edd4d 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -201,7 +201,6 @@
      * @return The animated value.
      */
     public Object getValue(float fraction) {
-
         // Special-case optimization for the common case of only two keyframes
         if (mNumKeyframes == 2) {
             if (mInterpolator != null) {
@@ -238,12 +237,13 @@
             Keyframe nextKeyframe = mKeyframes.get(i);
             if (fraction < nextKeyframe.getFraction()) {
                 final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
-                if (interpolator != null) {
-                    fraction = interpolator.getInterpolation(fraction);
-                }
                 final float prevFraction = prevKeyframe.getFraction();
                 float intervalFraction = (fraction - prevFraction) /
                     (nextKeyframe.getFraction() - prevFraction);
+                // Apply interpolator on the proportional duration.
+                if (interpolator != null) {
+                    intervalFraction = interpolator.getInterpolation(intervalFraction);
+                }
                 return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                         nextKeyframe.getValue());
             }
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 7526609..7b9a48c 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -27,10 +27,11 @@
 public class EventScheduler {
     private static final long NANOS_PER_MILLI = 1000000;
 
-    private final Object lock = new Object();
+    private final Object mLock = new Object();
     private SortedMap<Long, FastEventQueue> mEventBuffer;
     private FastEventQueue mEventPool = null;
     private int mMaxPoolSize = 200;
+    private boolean mClosed;
 
     public EventScheduler() {
         mEventBuffer = new TreeMap<Long, FastEventQueue>();
@@ -146,7 +147,7 @@
      * @param event
      */
     public void add(SchedulableEvent event) {
-        synchronized (lock) {
+        synchronized (mLock) {
             FastEventQueue list = mEventBuffer.get(event.getTimestamp());
             if (list == null) {
                 long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
@@ -156,7 +157,7 @@
                 // If the event we added is earlier than the previous earliest
                 // event then notify any threads waiting for the next event.
                 if (event.getTimestamp() < lowestTime) {
-                    lock.notify();
+                    mLock.notify();
                 }
             } else {
                 list.add(event);
@@ -183,7 +184,7 @@
      */
     public SchedulableEvent getNextEvent(long time) {
         SchedulableEvent event = null;
-        synchronized (lock) {
+        synchronized (mLock) {
             if (!mEventBuffer.isEmpty()) {
                 long lowestTime = mEventBuffer.firstKey();
                 // Is it time for this list to be processed?
@@ -206,9 +207,9 @@
      */
     public SchedulableEvent waitNextEvent() throws InterruptedException {
         SchedulableEvent event = null;
-        while (true) {
-            long millisToWait = Integer.MAX_VALUE;
-            synchronized (lock) {
+        synchronized (mLock) {
+            while (!mClosed) {
+                long millisToWait = Integer.MAX_VALUE;
                 if (!mEventBuffer.isEmpty()) {
                     long now = System.nanoTime();
                     long lowestTime = mEventBuffer.firstKey();
@@ -228,9 +229,16 @@
                         }
                     }
                 }
-                lock.wait((int) millisToWait);
+                mLock.wait((int) millisToWait);
             }
         }
         return event;
     }
+
+    public void close() {
+        synchronized (mLock) {
+            mClosed = true;
+            mLock.notify();
+        }
+    }
 }
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 3a1d3fc..42d70f6 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -28,10 +28,16 @@
     // 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 MidiReceiver mReceiver = new SchedulingReceiver();
 
-    private class SchedulingReceiver extends MidiReceiver
-    {
+    private final MidiReceiver[] mReceivers;
+
+    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.
@@ -41,12 +47,14 @@
                 throws IOException {
             MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
             if (event != null) {
+                event.portNumber = mPortNumber;
                 add(event);
             }
         }
     }
 
     public static class MidiEvent extends SchedulableEvent {
+        public int portNumber;
         public int count = 0;
         public byte[] data;
 
@@ -72,6 +80,17 @@
         }
     }
 
+    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.
      */
@@ -113,7 +132,15 @@
      * @return the MidiReceiver
      */
     public MidiReceiver getReceiver() {
-        return mReceiver;
+        return mReceivers[0];
+    }
+
+    /**
+     * This MidiReceiver will write date to the scheduling buffer.
+     * @return the MidiReceiver
+     */
+    public MidiReceiver getReceiver(int portNumber) {
+        return mReceivers[portNumber];
     }
 
 }
diff --git a/packages/DocumentsUI/res/menu/mode_directory.xml b/packages/DocumentsUI/res/menu/mode_directory.xml
index 0a3645f..695060d 100644
--- a/packages/DocumentsUI/res/menu/mode_directory.xml
+++ b/packages/DocumentsUI/res/menu/mode_directory.xml
@@ -29,4 +29,8 @@
         android:icon="@drawable/ic_menu_delete"
         android:title="@string/menu_delete"
         android:showAsAction="always" />
+    <item
+        android:id="@+id/menu_select_all"
+        android:title="@string/menu_select_all"
+        android:showAsAction="never" />
 </menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 268ce18..4ad337d 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -46,6 +46,8 @@
     <string name="menu_delete">Delete</string>
     <!-- Menu item title that selects the current directory [CHAR LIMIT=48] -->
     <string name="menu_select">Select \"<xliff:g id="directory" example="My Directory">^1</xliff:g>\"</string>
+    <!-- Menu item title that selects all documents in the current directory [CHAR LIMIT=24] -->
+    <string name="menu_select_all">Select All</string>
 
     <!-- Menu item that reveals internal storage built into the device [CHAR LIMIT=24] -->
     <string name="menu_advanced_show" product="nosdcard">Show internal storage</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index f55912c..a75dc42 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -501,6 +501,14 @@
                 mode.finish();
                 return true;
 
+            } else if (id == R.id.menu_select_all) {
+                int count = mCurrentView.getCount();
+                for (int i = 0; i < count; i++) {
+                    mCurrentView.setItemChecked(i, true);
+                }
+                updateDisplayState();
+                return true;
+
             } else {
                 return false;
             }
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 7c101a40f..6ece888 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -29,6 +29,9 @@
 import android.system.StructPollfd;
 import android.util.Log;
 
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+
 import libcore.io.IoUtils;
 
 import java.io.Closeable;
@@ -42,7 +45,7 @@
 
     private MidiDeviceServer mServer;
 
-    private final MidiReceiver[] mInputPortReceivers;
+    private final MidiEventScheduler mEventScheduler;
 
     private static final int BUFFER_SIZE = 512;
 
@@ -99,19 +102,7 @@
         for (int i = 0; i < outputCount; i++) {
             mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
         }
-
-        mInputPortReceivers = new MidiReceiver[inputCount];
-        for (int port = 0; port < inputCount; port++) {
-            final int portF = port;
-            mInputPortReceivers[port] = new MidiReceiver() {
-                @Override
-                public void onReceive(byte[] data, int offset, int count, long timestamp)
-                        throws IOException {
-                    // FIXME - timestamps are ignored, future posting not supported yet.
-                    mOutputStreams[portF].write(data, offset, count);
-                }
-            };
-        }
+        mEventScheduler = new MidiEventScheduler(inputCount);
     }
 
     private boolean register(Context context, Bundle properties) {
@@ -121,16 +112,22 @@
             return false;
         }
 
+        int inputCount = mInputStreams.length;
         int outputCount = mOutputStreams.length;
-        mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount,
+        MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
+        for (int port = 0; port < inputCount; port++) {
+            inputPortReceivers[port] = mEventScheduler.getReceiver(port);
+        }
+
+        mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
                 null, null, properties, MidiDeviceInfo.TYPE_USB, null);
         if (mServer == null) {
             return false;
         }
         final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
 
-        // FIXME can we only run this when we have a dispatcher that has listeners?
-        new Thread() {
+        // Create input thread
+        new Thread("UsbMidiDevice input thread") {
             @Override
             public void run() {
                 byte[] buffer = new byte[BUFFER_SIZE];
@@ -160,6 +157,33 @@
                 } catch (ErrnoException e) {
                     Log.d(TAG, "reader thread exiting");
                 }
+                Log.d(TAG, "input thread exit");
+            }
+        }.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;
+                    }
+                    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");
             }
         }.start();
 
@@ -168,6 +192,8 @@
 
     @Override
     public void close() throws IOException {
+        mEventScheduler.close();
+
         if (mServer != null) {
             mServer.close();
         }