Even more native input dispatch work in progress.

Added more tests.
Fixed a regression in Vector.
Fixed bugs in pointer tracking.
Fixed a starvation issue in PollLoop when setting or removing callbacks.
Fixed a couple of policy nits.

Modified the internal representation of MotionEvent to be more
efficient and more consistent.

Added code to skip/cancel virtual key processing when there are multiple
pointers down.  This helps to better disambiguate virtual key presses
from stray touches (such as cheek presses).

Change-Id: I2a7d2cce0195afb9125b23378baa94fd2fc6671c
diff --git a/api/current.xml b/api/current.xml
index 0db40ce..6847352 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -176769,7 +176769,7 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="downTime" type="long">
@@ -176904,7 +176904,7 @@
  native="false"
  synchronized="false"
  static="false"
- final="false"
+ final="true"
  deprecated="not deprecated"
  visibility="public"
 >
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 1f06191..ae8c21d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -27,6 +27,7 @@
  * it is being used for.
  */
 public final class MotionEvent implements Parcelable {
+    private static final long MS_PER_NS = 1000000;
     static final boolean DEBUG_POINTERS = false;
     
     /**
@@ -218,31 +219,32 @@
     static private int gRecyclerUsed = 0;
     static private MotionEvent gRecyclerTop = null;
 
-    private long mDownTime;
-    private long mEventTimeNano;
+    private long mDownTimeNano;
     private int mAction;
-    private float mRawX;
-    private float mRawY;
+    private float mXOffset;
+    private float mYOffset;
     private float mXPrecision;
     private float mYPrecision;
     private int mDeviceId;
     private int mEdgeFlags;
     private int mMetaState;
     
-    // Here is the actual event data.  Note that the order of the array
-    // is a little odd: the first entry is the most recent, and the ones
-    // following it are the historical data from oldest to newest.  This
-    // allows us to easily retrieve the most recent data, without having
-    // to copy the arrays every time a new sample is added.
-    
     private int mNumPointers;
     private int mNumSamples;
+    
+    private int mLastDataSampleIndex;
+    private int mLastEventTimeNanoSampleIndex;
+    
     // Array of mNumPointers size of identifiers for each pointer of data.
     private int[] mPointerIdentifiers;
+    
     // Array of (mNumSamples * mNumPointers * NUM_SAMPLE_DATA) size of event data.
+    // Samples are ordered from oldest to newest.
     private float[] mDataSamples;
-    // Array of mNumSamples size of time stamps.
-    private long[] mTimeSamples;
+    
+    // Array of mNumSamples size of event time stamps in nanoseconds.
+    // Samples are ordered from oldest to newest.
+    private long[] mEventTimeNanoSamples;
 
     private MotionEvent mNext;
     private RuntimeException mRecycledLocation;
@@ -251,26 +253,9 @@
     private MotionEvent(int pointerCount, int sampleCount) {
         mPointerIdentifiers = new int[pointerCount];
         mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA];
-        mTimeSamples = new long[sampleCount];
+        mEventTimeNanoSamples = new long[sampleCount];
     }
 
-    static private MotionEvent obtain() {
-        final MotionEvent ev;
-        synchronized (gRecyclerLock) {
-            if (gRecyclerTop == null) {
-                return new MotionEvent(BASE_AVAIL_POINTERS, BASE_AVAIL_SAMPLES);
-            }
-            ev = gRecyclerTop;
-            gRecyclerTop = ev.mNext;
-            gRecyclerUsed--;
-        }
-        ev.mRecycledLocation = null;
-        ev.mRecycled = false;
-        ev.mNext = null;
-        return ev;
-    }
-    
-    @SuppressWarnings("unused") // used by native code
     static private MotionEvent obtain(int pointerCount, int sampleCount) {
         final MotionEvent ev;
         synchronized (gRecyclerLock) {
@@ -285,7 +270,7 @@
             }
             ev = gRecyclerTop;
             gRecyclerTop = ev.mNext;
-            gRecyclerUsed--;
+            gRecyclerUsed -= 1;
         }
         ev.mRecycledLocation = null;
         ev.mRecycled = false;
@@ -295,20 +280,18 @@
             ev.mPointerIdentifiers = new int[pointerCount];
         }
         
-        final int timeSamplesLength = ev.mTimeSamples.length;
-        if (timeSamplesLength < sampleCount) {
-            ev.mTimeSamples = new long[sampleCount];
+        if (ev.mEventTimeNanoSamples.length < sampleCount) {
+            ev.mEventTimeNanoSamples = new long[sampleCount];
         }
         
-        final int dataSamplesLength = ev.mDataSamples.length;
         final int neededDataSamplesLength = pointerCount * sampleCount * NUM_SAMPLE_DATA;
-        if (dataSamplesLength < neededDataSamplesLength) {
+        if (ev.mDataSamples.length < neededDataSamplesLength) {
             ev.mDataSamples = new float[neededDataSamplesLength];
         }
         
         return ev;
     }
-
+    
     /**
      * Create a new MotionEvent, filling in all of the basic values that
      * define the motion.
@@ -342,45 +325,39 @@
     static public MotionEvent obtainNano(long downTime, long eventTime, long eventTimeNano,
             int action, int pointers, int[] inPointerIds, float[] inData, int metaState,
             float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
-        MotionEvent ev = obtain();
+        MotionEvent ev = obtain(pointers, 1);
         ev.mDeviceId = deviceId;
         ev.mEdgeFlags = edgeFlags;
-        ev.mDownTime = downTime;
-        ev.mEventTimeNano = eventTimeNano;
+        ev.mDownTimeNano = downTime * MS_PER_NS;
         ev.mAction = action;
         ev.mMetaState = metaState;
-        ev.mRawX = inData[SAMPLE_X];
-        ev.mRawY = inData[SAMPLE_Y];
+        ev.mXOffset = 0;
+        ev.mYOffset = 0;
         ev.mXPrecision = xPrecision;
         ev.mYPrecision = yPrecision;
+        
         ev.mNumPointers = pointers;
         ev.mNumSamples = 1;
         
-        int[] pointerIdentifiers = ev.mPointerIdentifiers;
-        if (pointerIdentifiers.length < pointers) {
-            ev.mPointerIdentifiers = pointerIdentifiers = new int[pointers];
-        }
-        System.arraycopy(inPointerIds, 0, pointerIdentifiers, 0, pointers);
+        ev.mLastDataSampleIndex = 0;
+        ev.mLastEventTimeNanoSampleIndex = 0;
         
-        final int ND = pointers * NUM_SAMPLE_DATA;
-        float[] dataSamples = ev.mDataSamples;
-        if (dataSamples.length < ND) {
-            ev.mDataSamples = dataSamples = new float[ND];
-        }
-        System.arraycopy(inData, 0, dataSamples, 0, ND);
+        System.arraycopy(inPointerIds, 0, ev.mPointerIdentifiers, 0, pointers);
         
-        ev.mTimeSamples[0] = eventTime;
+        ev.mEventTimeNanoSamples[0] = eventTimeNano;
+        
+        System.arraycopy(inData, 0, ev.mDataSamples, 0, pointers * NUM_SAMPLE_DATA);
 
         if (DEBUG_POINTERS) {
             StringBuilder sb = new StringBuilder(128);
             sb.append("New:");
-            for (int i=0; i<pointers; i++) {
+            for (int i = 0; i < pointers; i++) {
                 sb.append(" #");
-                sb.append(ev.mPointerIdentifiers[i]);
+                sb.append(ev.getPointerId(i));
                 sb.append("(");
-                sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]);
+                sb.append(ev.getX(i));
                 sb.append(",");
-                sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]);
+                sb.append(ev.getY(i));
                 sb.append(")");
             }
             Log.v("MotionEvent", sb.toString());
@@ -423,27 +400,32 @@
     static public MotionEvent obtain(long downTime, long eventTime, int action,
             float x, float y, float pressure, float size, int metaState,
             float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
-        MotionEvent ev = obtain();
+        MotionEvent ev = obtain(1, 1);
         ev.mDeviceId = deviceId;
         ev.mEdgeFlags = edgeFlags;
-        ev.mDownTime = downTime;
-        ev.mEventTimeNano = eventTime * 1000000;
+        ev.mDownTimeNano = downTime * MS_PER_NS;
         ev.mAction = action;
         ev.mMetaState = metaState;
+        ev.mXOffset = 0;
+        ev.mYOffset = 0;
         ev.mXPrecision = xPrecision;
         ev.mYPrecision = yPrecision;
-
+        
         ev.mNumPointers = 1;
         ev.mNumSamples = 1;
-        int[] pointerIds = ev.mPointerIdentifiers;
-        pointerIds[0] = 0;
-        float[] data = ev.mDataSamples;
-        data[SAMPLE_X] = ev.mRawX = x;
-        data[SAMPLE_Y] = ev.mRawY = y;
-        data[SAMPLE_PRESSURE] = pressure;
-        data[SAMPLE_SIZE] = size;
-        ev.mTimeSamples[0] = eventTime;
-
+        
+        ev.mLastDataSampleIndex = 0;
+        ev.mLastEventTimeNanoSampleIndex = 0;
+        
+        ev.mPointerIdentifiers[0] = 0;
+        
+        ev.mEventTimeNanoSamples[0] = eventTime * MS_PER_NS;
+        
+        float[] dataSamples = ev.mDataSamples;
+        dataSamples[SAMPLE_X] = x;
+        dataSamples[SAMPLE_Y] = y;
+        dataSamples[SAMPLE_PRESSURE] = pressure;
+        dataSamples[SAMPLE_SIZE] = size;
         return ev;
     }
 
@@ -478,33 +460,16 @@
      * numbers are arbitrary and you shouldn't depend on the values.
      * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
      * MotionEvent.
+     * 
+     * @deprecated Use {@link #obtain(long, long, int, float, float, float, float, int, float, float, int, int)}
+     * instead.
      */
+    @Deprecated
     static public MotionEvent obtain(long downTime, long eventTime, int action,
             int pointers, float x, float y, float pressure, float size, int metaState,
             float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
-        MotionEvent ev = obtain();
-        ev.mDeviceId = deviceId;
-        ev.mEdgeFlags = edgeFlags;
-        ev.mDownTime = downTime;
-        ev.mEventTimeNano = eventTime * 1000000;
-        ev.mAction = action;
-        ev.mNumPointers = pointers;
-        ev.mMetaState = metaState;
-        ev.mXPrecision = xPrecision;
-        ev.mYPrecision = yPrecision;
-
-        ev.mNumPointers = 1;
-        ev.mNumSamples = 1;
-        int[] pointerIds = ev.mPointerIdentifiers;
-        pointerIds[0] = 0;
-        float[] data = ev.mDataSamples;
-        data[SAMPLE_X] = ev.mRawX = x;
-        data[SAMPLE_Y] = ev.mRawY = y;
-        data[SAMPLE_PRESSURE] = pressure;
-        data[SAMPLE_SIZE] = size;
-        ev.mTimeSamples[0] = eventTime;
-
-        return ev;
+        return obtain(downTime, eventTime, action, x, y, pressure, size,
+                metaState, xPrecision, yPrecision, deviceId, edgeFlags);
     }
 
     /**
@@ -526,89 +491,36 @@
      */
     static public MotionEvent obtain(long downTime, long eventTime, int action,
             float x, float y, int metaState) {
-        MotionEvent ev = obtain();
-        ev.mDeviceId = 0;
-        ev.mEdgeFlags = 0;
-        ev.mDownTime = downTime;
-        ev.mEventTimeNano = eventTime * 1000000;
-        ev.mAction = action;
-        ev.mNumPointers = 1;
-        ev.mMetaState = metaState;
-        ev.mXPrecision = 1.0f;
-        ev.mYPrecision = 1.0f;
-
-        ev.mNumPointers = 1;
-        ev.mNumSamples = 1;
-        int[] pointerIds = ev.mPointerIdentifiers;
-        pointerIds[0] = 0;
-        float[] data = ev.mDataSamples;
-        data[SAMPLE_X] = ev.mRawX = x;
-        data[SAMPLE_Y] = ev.mRawY = y;
-        data[SAMPLE_PRESSURE] = 1.0f;
-        data[SAMPLE_SIZE] = 1.0f;
-        ev.mTimeSamples[0] = eventTime;
-
-        return ev;
-    }
-
-    /**
-     * Scales down the coordination of this event by the given scale.
-     *
-     * @hide
-     */
-    public void scale(float scale) {
-        mRawX *= scale;
-        mRawY *= scale;
-        mXPrecision *= scale;
-        mYPrecision *= scale;
-        float[] history = mDataSamples;
-        final int length = mNumPointers * mNumSamples * NUM_SAMPLE_DATA;
-        for (int i = 0; i < length; i += NUM_SAMPLE_DATA) {
-            history[i + SAMPLE_X] *= scale;
-            history[i + SAMPLE_Y] *= scale;
-            // no need to scale pressure
-            history[i + SAMPLE_SIZE] *= scale;    // TODO: square this?
-        }
+        return obtain(downTime, eventTime, action, x, y, 1.0f, 1.0f,
+                metaState, 1.0f, 1.0f, 0, 0);
     }
 
     /**
      * Create a new MotionEvent, copying from an existing one.
      */
     static public MotionEvent obtain(MotionEvent o) {
-        MotionEvent ev = obtain();
+        MotionEvent ev = obtain(o.mNumPointers, o.mNumSamples);
         ev.mDeviceId = o.mDeviceId;
         ev.mEdgeFlags = o.mEdgeFlags;
-        ev.mDownTime = o.mDownTime;
-        ev.mEventTimeNano = o.mEventTimeNano;
+        ev.mDownTimeNano = o.mDownTimeNano;
         ev.mAction = o.mAction;
-        ev.mNumPointers = o.mNumPointers;
-        ev.mRawX = o.mRawX;
-        ev.mRawY = o.mRawY;
         ev.mMetaState = o.mMetaState;
+        ev.mXOffset = o.mXOffset;
+        ev.mYOffset = o.mYOffset;
         ev.mXPrecision = o.mXPrecision;
         ev.mYPrecision = o.mYPrecision;
+        int numPointers = ev.mNumPointers = o.mNumPointers;
+        int numSamples = ev.mNumSamples = o.mNumSamples;
         
-        final int NS = ev.mNumSamples = o.mNumSamples;
-        if (ev.mTimeSamples.length >= NS) {
-            System.arraycopy(o.mTimeSamples, 0, ev.mTimeSamples, 0, NS);
-        } else {
-            ev.mTimeSamples = (long[])o.mTimeSamples.clone();
-        }
+        ev.mLastDataSampleIndex = o.mLastDataSampleIndex;
+        ev.mLastEventTimeNanoSampleIndex = o.mLastEventTimeNanoSampleIndex;
         
-        final int NP = (ev.mNumPointers=o.mNumPointers);
-        if (ev.mPointerIdentifiers.length >= NP) {
-            System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, NP);
-        } else {
-            ev.mPointerIdentifiers = (int[])o.mPointerIdentifiers.clone();
-        }
+        System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, numPointers);
         
-        final int ND = NP * NS * NUM_SAMPLE_DATA;
-        if (ev.mDataSamples.length >= ND) {
-            System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, ND);
-        } else {
-            ev.mDataSamples = (float[])o.mDataSamples.clone();
-        }
+        System.arraycopy(o.mEventTimeNanoSamples, 0, ev.mEventTimeNanoSamples, 0, numSamples);
         
+        System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0,
+                numPointers * numSamples * NUM_SAMPLE_DATA);
         return ev;
     }
 
@@ -617,36 +529,29 @@
      * any historical point information.
      */
     static public MotionEvent obtainNoHistory(MotionEvent o) {
-        MotionEvent ev = obtain();
+        MotionEvent ev = obtain(o.mNumPointers, 1);
         ev.mDeviceId = o.mDeviceId;
         ev.mEdgeFlags = o.mEdgeFlags;
-        ev.mDownTime = o.mDownTime;
-        ev.mEventTimeNano = o.mEventTimeNano;
+        ev.mDownTimeNano = o.mDownTimeNano;
         ev.mAction = o.mAction;
-        ev.mNumPointers = o.mNumPointers;
-        ev.mRawX = o.mRawX;
-        ev.mRawY = o.mRawY;
         ev.mMetaState = o.mMetaState;
+        ev.mXOffset = o.mXOffset;
+        ev.mYOffset = o.mYOffset;
         ev.mXPrecision = o.mXPrecision;
         ev.mYPrecision = o.mYPrecision;
         
+        int numPointers = ev.mNumPointers = o.mNumPointers;
         ev.mNumSamples = 1;
-        ev.mTimeSamples[0] = o.mTimeSamples[0];
         
-        final int NP = (ev.mNumPointers=o.mNumPointers);
-        if (ev.mPointerIdentifiers.length >= NP) {
-            System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, NP);
-        } else {
-            ev.mPointerIdentifiers = (int[])o.mPointerIdentifiers.clone();
-        }
+        ev.mLastDataSampleIndex = 0;
+        ev.mLastEventTimeNanoSampleIndex = 0;
         
-        final int ND = NP * NUM_SAMPLE_DATA;
-        if (ev.mDataSamples.length >= ND) {
-            System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, ND);
-        } else {
-            ev.mDataSamples = (float[])o.mDataSamples.clone();
-        }
+        System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, numPointers);
         
+        ev.mEventTimeNanoSamples[0] = o.mEventTimeNanoSamples[o.mLastEventTimeNanoSampleIndex];
+        
+        System.arraycopy(o.mDataSamples, o.mLastDataSampleIndex, ev.mDataSamples, 0,
+                numPointers * NUM_SAMPLE_DATA);
         return ev;
     }
 
@@ -654,7 +559,7 @@
      * Recycle the MotionEvent, to be re-used by a later caller.  After calling
      * this function you must not ever touch the event again.
      */
-    public void recycle() {
+    public final void recycle() {
         // Ensure recycle is only called once!
         if (TRACK_RECYCLED_LOCATION) {
             if (mRecycledLocation != null) {
@@ -678,6 +583,27 @@
             }
         }
     }
+    
+    /**
+     * Scales down the coordination of this event by the given scale.
+     *
+     * @hide
+     */
+    public final void scale(float scale) {
+        mXOffset *= scale;
+        mYOffset *= scale;
+        mXPrecision *= scale;
+        mYPrecision *= scale;
+        
+        float[] history = mDataSamples;
+        final int length = mNumPointers * mNumSamples * NUM_SAMPLE_DATA;
+        for (int i = 0; i < length; i += NUM_SAMPLE_DATA) {
+            history[i + SAMPLE_X] *= scale;
+            history[i + SAMPLE_Y] *= scale;
+            // no need to scale pressure
+            history[i + SAMPLE_SIZE] *= scale;    // TODO: square this?
+        }
+    }
 
     /**
      * Return the kind of action being performed -- one of either
@@ -719,14 +645,14 @@
      * a stream of position events.
      */
     public final long getDownTime() {
-        return mDownTime;
+        return mDownTimeNano / MS_PER_NS;
     }
 
     /**
      * Returns the time (in ms) when this specific event was generated.
      */
     public final long getEventTime() {
-        return mTimeSamples[0];
+        return mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] / MS_PER_NS;
     }
 
     /**
@@ -736,7 +662,7 @@
      * @hide
      */
     public final long getEventTimeNano() {
-        return mEventTimeNano;
+        return mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex];
     }
 
     /**
@@ -744,7 +670,7 @@
      * arbitrary pointer identifier).
      */
     public final float getX() {
-        return mDataSamples[SAMPLE_X];
+        return mDataSamples[mLastDataSampleIndex + SAMPLE_X] + mXOffset;
     }
 
     /**
@@ -752,7 +678,7 @@
      * arbitrary pointer identifier).
      */
     public final float getY() {
-        return mDataSamples[SAMPLE_Y];
+        return mDataSamples[mLastDataSampleIndex + SAMPLE_Y] + mYOffset;
     }
 
     /**
@@ -760,7 +686,7 @@
      * arbitrary pointer identifier).
      */
     public final float getPressure() {
-        return mDataSamples[SAMPLE_PRESSURE];
+        return mDataSamples[mLastDataSampleIndex + SAMPLE_PRESSURE];
     }
 
     /**
@@ -768,7 +694,7 @@
      * arbitrary pointer identifier).
      */
     public final float getSize() {
-        return mDataSamples[SAMPLE_SIZE];
+        return mDataSamples[mLastDataSampleIndex + SAMPLE_SIZE];
     }
 
     /**
@@ -820,7 +746,8 @@
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
     public final float getX(int pointerIndex) {
-        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_X];
+        return mDataSamples[mLastDataSampleIndex
+                            + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset;
     }
 
     /**
@@ -833,7 +760,8 @@
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
     public final float getY(int pointerIndex) {
-        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_Y];
+        return mDataSamples[mLastDataSampleIndex
+                            + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset;
     }
 
     /**
@@ -848,7 +776,8 @@
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
     public final float getPressure(int pointerIndex) {
-        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
+        return mDataSamples[mLastDataSampleIndex
+                            + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_PRESSURE];
     }
 
     /**
@@ -864,7 +793,8 @@
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
     public final float getSize(int pointerIndex) {
-        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_SIZE];
+        return mDataSamples[mLastDataSampleIndex
+                            + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_SIZE];
     }
 
     /**
@@ -888,7 +818,7 @@
      * and views.
      */
     public final float getRawX() {
-        return mRawX;
+        return mDataSamples[mLastDataSampleIndex + SAMPLE_X];
     }
 
     /**
@@ -898,7 +828,7 @@
      * and views.
      */
     public final float getRawY() {
-        return mRawY;
+        return mDataSamples[mLastDataSampleIndex + SAMPLE_Y];
     }
 
     /**
@@ -930,7 +860,7 @@
      * @return Returns the number of historical points in the event.
      */
     public final int getHistorySize() {
-        return mNumSamples - 1;
+        return mLastEventTimeNanoSampleIndex;
     }
 
     /**
@@ -944,7 +874,7 @@
      * @see #getEventTime
      */
     public final long getHistoricalEventTime(int pos) {
-        return mTimeSamples[pos + 1];
+        return mEventTimeNanoSamples[pos] / MS_PER_NS;
     }
 
     /**
@@ -952,7 +882,7 @@
      * arbitrary pointer identifier).
      */
     public final float getHistoricalX(int pos) {
-        return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_X];
+        return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset;
     }
 
     /**
@@ -960,7 +890,7 @@
      * arbitrary pointer identifier).
      */
     public final float getHistoricalY(int pos) {
-        return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_Y];
+        return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset;
     }
 
     /**
@@ -968,7 +898,7 @@
      * arbitrary pointer identifier).
      */
     public final float getHistoricalPressure(int pos) {
-        return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_PRESSURE];
+        return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_PRESSURE];
     }
 
     /**
@@ -976,7 +906,7 @@
      * arbitrary pointer identifier).
      */
     public final float getHistoricalSize(int pos) {
-        return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_SIZE];
+        return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_SIZE];
     }
 
     /**
@@ -993,8 +923,8 @@
      * @see #getX
      */
     public final float getHistoricalX(int pointerIndex, int pos) {
-        return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_X];
+        return mDataSamples[(pos * mNumPointers + pointerIndex)
+                            * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset;
     }
 
     /**
@@ -1011,8 +941,8 @@
      * @see #getY
      */
     public final float getHistoricalY(int pointerIndex, int pos) {
-        return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_Y];
+        return mDataSamples[(pos * mNumPointers + pointerIndex)
+                            * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset;
     }
 
     /**
@@ -1029,8 +959,8 @@
      * @see #getPressure
      */
     public final float getHistoricalPressure(int pointerIndex, int pos) {
-        return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
+        return mDataSamples[(pos * mNumPointers + pointerIndex)
+                            * NUM_SAMPLE_DATA + SAMPLE_PRESSURE];
     }
 
     /**
@@ -1047,8 +977,8 @@
      * @see #getSize
      */
     public final float getHistoricalSize(int pointerIndex, int pos) {
-        return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_SIZE];
+        return mDataSamples[(pos * mNumPointers + pointerIndex)
+                            * NUM_SAMPLE_DATA + SAMPLE_SIZE];
     }
 
     /**
@@ -1098,12 +1028,8 @@
      * @param deltaY Amount to add to the current Y coordinate of the event.
      */
     public final void offsetLocation(float deltaX, float deltaY) {
-        final int N = mNumPointers*mNumSamples*4;
-        final float[] pos = mDataSamples;
-        for (int i=0; i<N; i+=NUM_SAMPLE_DATA) {
-            pos[i+SAMPLE_X] += deltaX;
-            pos[i+SAMPLE_Y] += deltaY;
-        }
+        mXOffset += deltaX;
+        mYOffset += deltaY;
     }
 
     /**
@@ -1114,11 +1040,28 @@
      * @param y New absolute Y location.
      */
     public final void setLocation(float x, float y) {
-        float deltaX = x-mDataSamples[SAMPLE_X];
-        float deltaY = y-mDataSamples[SAMPLE_Y];
-        if (deltaX != 0 || deltaY != 0) {
-            offsetLocation(deltaX, deltaY);
+        mXOffset = x - mDataSamples[mLastDataSampleIndex + SAMPLE_X];
+        mYOffset = y - mDataSamples[mLastDataSampleIndex + SAMPLE_Y];
+    }
+    
+    private final void incrementNumSamplesAndReserveStorage(int dataSampleStride) {
+        if (mNumSamples == mEventTimeNanoSamples.length) {
+            long[] newEventTimeNanoSamples = new long[mNumSamples + BASE_AVAIL_SAMPLES];
+            System.arraycopy(mEventTimeNanoSamples, 0, newEventTimeNanoSamples, 0, mNumSamples);
+            mEventTimeNanoSamples = newEventTimeNanoSamples;
         }
+        
+        int nextDataSampleIndex = mLastDataSampleIndex + dataSampleStride;
+        if (nextDataSampleIndex + dataSampleStride > mDataSamples.length) {
+            float[] newDataSamples = new float[nextDataSampleIndex
+                                               + BASE_AVAIL_SAMPLES * dataSampleStride];
+            System.arraycopy(mDataSamples, 0, newDataSamples, 0, nextDataSampleIndex);
+            mDataSamples = newDataSamples;
+        }
+        
+        mLastEventTimeNanoSampleIndex = mNumSamples;
+        mLastDataSampleIndex = nextDataSampleIndex;
+        mNumSamples += 1;
     }
 
     /**
@@ -1136,42 +1079,16 @@
      */
     public final void addBatch(long eventTime, float x, float y,
             float pressure, float size, int metaState) {
-        float[] data = mDataSamples;
-        long[] times = mTimeSamples;
+        incrementNumSamplesAndReserveStorage(NUM_SAMPLE_DATA);
         
-        final int NP = mNumPointers;
-        final int NS = mNumSamples;
-        final int NI = NP*NS;
-        final int ND = NI * NUM_SAMPLE_DATA;
-        if (data.length <= ND) {
-            final int NEW_ND = ND + (NP * (BASE_AVAIL_SAMPLES * NUM_SAMPLE_DATA));
-            float[] newData = new float[NEW_ND];
-            System.arraycopy(data, 0, newData, 0, ND);
-            mDataSamples = data = newData;
-        }
-        if (times.length <= NS) {
-            final int NEW_NS = NS + BASE_AVAIL_SAMPLES;
-            long[] newHistoryTimes = new long[NEW_NS];
-            System.arraycopy(times, 0, newHistoryTimes, 0, NS);
-            mTimeSamples = times = newHistoryTimes;
-        }
+        mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] = eventTime * MS_PER_NS;
         
-        times[NS] = times[0];
-        times[0] = eventTime;
+        float[] dataSamples = mDataSamples;
+        dataSamples[mLastDataSampleIndex + SAMPLE_X] = x - mXOffset;
+        dataSamples[mLastDataSampleIndex + SAMPLE_Y] = y - mYOffset;
+        dataSamples[mLastDataSampleIndex + SAMPLE_PRESSURE] = pressure;
+        dataSamples[mLastDataSampleIndex + SAMPLE_SIZE] = size;
         
-        final int pos = NS*NUM_SAMPLE_DATA;
-        data[pos+SAMPLE_X] = data[SAMPLE_X];
-        data[pos+SAMPLE_Y] = data[SAMPLE_Y];
-        data[pos+SAMPLE_PRESSURE] = data[SAMPLE_PRESSURE];
-        data[pos+SAMPLE_SIZE] = data[SAMPLE_SIZE];
-        data[SAMPLE_X] = x;
-        data[SAMPLE_Y] = y;
-        data[SAMPLE_PRESSURE] = pressure;
-        data[SAMPLE_SIZE] = size;
-        mNumSamples = NS+1;
-
-        mRawX = x;
-        mRawY = y;
         mMetaState |= metaState;
     }
 
@@ -1187,48 +1104,36 @@
      * @hide
      */
     public final void addBatch(long eventTime, float[] inData, int metaState) {
-        float[] data = mDataSamples;
-        long[] times = mTimeSamples;
+        final int numPointers = mNumPointers;
+        final int dataSampleStride = numPointers * NUM_SAMPLE_DATA;
+        incrementNumSamplesAndReserveStorage(dataSampleStride);
         
-        final int NP = mNumPointers;
-        final int NS = mNumSamples;
-        final int NI = NP*NS;
-        final int ND = NI * NUM_SAMPLE_DATA;
-        if (data.length < (ND+(NP*NUM_SAMPLE_DATA))) {
-            final int NEW_ND = ND + (NP * (BASE_AVAIL_SAMPLES * NUM_SAMPLE_DATA));
-            float[] newData = new float[NEW_ND];
-            System.arraycopy(data, 0, newData, 0, ND);
-            mDataSamples = data = newData;
-        }
-        if (times.length < (NS+1)) {
-            final int NEW_NS = NS + BASE_AVAIL_SAMPLES;
-            long[] newHistoryTimes = new long[NEW_NS];
-            System.arraycopy(times, 0, newHistoryTimes, 0, NS);
-            mTimeSamples = times = newHistoryTimes;
-        }
+        mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] = eventTime * MS_PER_NS;
         
-        times[NS] = times[0];
-        times[0] = eventTime;
-        
-        System.arraycopy(data, 0, data, ND, mNumPointers*NUM_SAMPLE_DATA);
-        System.arraycopy(inData, 0, data, 0, mNumPointers*NUM_SAMPLE_DATA);
-        
-        mNumSamples = NS+1;
+        float[] dataSamples = mDataSamples;
+        System.arraycopy(inData, 0, dataSamples, mLastDataSampleIndex, dataSampleStride);
 
-        mRawX = inData[SAMPLE_X];
-        mRawY = inData[SAMPLE_Y];
+        if (mXOffset != 0 || mYOffset != 0) {
+            int index = mLastEventTimeNanoSampleIndex;
+            for (int i = 0; i < numPointers; i++) {
+                dataSamples[index + SAMPLE_X] -= mXOffset;
+                dataSamples[index + SAMPLE_Y] -= mYOffset;
+                index += NUM_SAMPLE_DATA;
+            }
+        }
+        
         mMetaState |= metaState;
         
         if (DEBUG_POINTERS) {
             StringBuilder sb = new StringBuilder(128);
             sb.append("Add:");
-            for (int i=0; i<mNumPointers; i++) {
+            for (int i = 0; i < mNumPointers; i++) {
                 sb.append(" #");
-                sb.append(mPointerIdentifiers[i]);
+                sb.append(getPointerId(i));
                 sb.append("(");
-                sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]);
+                sb.append(getX(i));
                 sb.append(",");
-                sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]);
+                sb.append(getY(i));
                 sb.append(")");
             }
             Log.v("MotionEvent", sb.toString());
@@ -1245,8 +1150,41 @@
     public static final Parcelable.Creator<MotionEvent> CREATOR
             = new Parcelable.Creator<MotionEvent>() {
         public MotionEvent createFromParcel(Parcel in) {
-            MotionEvent ev = obtain();
-            ev.readFromParcel(in);
+            final int NP = in.readInt();
+            final int NS = in.readInt();
+            final int NI = NP * NS * NUM_SAMPLE_DATA;
+            
+            MotionEvent ev = obtain(NP, NS);
+            ev.mNumPointers = NP;
+            ev.mNumSamples = NS;
+            
+            ev.mDownTimeNano = in.readLong();
+            ev.mAction = in.readInt();
+            ev.mXOffset = in.readFloat();
+            ev.mYOffset = in.readFloat();
+            ev.mXPrecision = in.readFloat();
+            ev.mYPrecision = in.readFloat();
+            ev.mDeviceId = in.readInt();
+            ev.mEdgeFlags = in.readInt();
+            ev.mMetaState = in.readInt();
+            
+            final int[] pointerIdentifiers = ev.mPointerIdentifiers;
+            for (int i = 0; i < NP; i++) {
+                pointerIdentifiers[i] = in.readInt();
+            }
+            
+            final long[] eventTimeNanoSamples = ev.mEventTimeNanoSamples;
+            for (int i = 0; i < NS; i++) {
+                eventTimeNanoSamples[i] = in.readLong();
+            }
+
+            final float[] dataSamples = ev.mDataSamples;
+            for (int i = 0; i < NI; i++) {
+                dataSamples[i] = in.readFloat();
+            }
+            
+            ev.mLastEventTimeNanoSampleIndex = NS - 1;
+            ev.mLastDataSampleIndex = (NS - 1) * NP * NUM_SAMPLE_DATA;
             return ev;
         }
 
@@ -1260,79 +1198,36 @@
     }
 
     public void writeToParcel(Parcel out, int flags) {
-        out.writeLong(mDownTime);
-        out.writeLong(mEventTimeNano);
-        out.writeInt(mAction);
-        out.writeInt(mMetaState);
-        out.writeFloat(mRawX);
-        out.writeFloat(mRawY);
         final int NP = mNumPointers;
-        out.writeInt(NP);
         final int NS = mNumSamples;
+        final int NI = NP * NS * NUM_SAMPLE_DATA;
+        
+        out.writeInt(NP);
         out.writeInt(NS);
-        final int NI = NP*NS;
-        if (NI > 0) {
-            int i;
-            int[] state = mPointerIdentifiers;
-            for (i=0; i<NP; i++) {
-                out.writeInt(state[i]);
-            }
-            final int ND = NI*NUM_SAMPLE_DATA;
-            float[] history = mDataSamples;
-            for (i=0; i<ND; i++) {
-                out.writeFloat(history[i]);
-            }
-            long[] times = mTimeSamples;
-            for (i=0; i<NS; i++) {
-                out.writeLong(times[i]);
-            }
-        }
+        
+        out.writeLong(mDownTimeNano);
+        out.writeInt(mAction);
+        out.writeFloat(mXOffset);
+        out.writeFloat(mYOffset);
         out.writeFloat(mXPrecision);
         out.writeFloat(mYPrecision);
         out.writeInt(mDeviceId);
         out.writeInt(mEdgeFlags);
-    }
-
-    private void readFromParcel(Parcel in) {
-        mDownTime = in.readLong();
-        mEventTimeNano = in.readLong();
-        mAction = in.readInt();
-        mMetaState = in.readInt();
-        mRawX = in.readFloat();
-        mRawY = in.readFloat();
-        final int NP = in.readInt();
-        mNumPointers = NP;
-        final int NS = in.readInt();
-        mNumSamples = NS;
-        final int NI = NP*NS;
-        if (NI > 0) {
-            int[] ids = mPointerIdentifiers;
-            if (ids.length < NP) {
-                mPointerIdentifiers = ids = new int[NP];
-            }
-            for (int i=0; i<NP; i++) {
-                ids[i] = in.readInt();
-            }
-            float[] history = mDataSamples;
-            final int ND = NI*NUM_SAMPLE_DATA;
-            if (history.length < ND) {
-                mDataSamples = history = new float[ND];
-            }
-            for (int i=0; i<ND; i++) {
-                history[i] = in.readFloat();
-            }
-            long[] times = mTimeSamples;
-            if (times == null || times.length < NS) {
-                mTimeSamples = times = new long[NS];
-            }
-            for (int i=0; i<NS; i++) {
-                times[i] = in.readLong();
-            }
+        out.writeInt(mMetaState);
+        
+        final int[] pointerIdentifiers = mPointerIdentifiers;
+        for (int i = 0; i < NP; i++) {
+            out.writeInt(pointerIdentifiers[i]);
         }
-        mXPrecision = in.readFloat();
-        mYPrecision = in.readFloat();
-        mDeviceId = in.readInt();
-        mEdgeFlags = in.readInt();
-    }
+        
+        final long[] eventTimeNanoSamples = mEventTimeNanoSamples;
+        for (int i = 0; i < NS; i++) {
+            out.writeLong(eventTimeNanoSamples[i]);
+        }
 
+        final float[] dataSamples = mDataSamples;
+        for (int i = 0; i < NI; i++) {
+            out.writeFloat(dataSamples[i]);
+        }
+    }
 }
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 47bb073..4a4393a 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -121,9 +121,9 @@
     String8 name(nameChars);
     env->ReleaseStringUTFChars(nameObj, nameChars);
 
-    InputChannel* serverChannel;
-    InputChannel* clientChannel;
-    status_t result = InputChannel::openInputChannelPair(name, & serverChannel, & clientChannel);
+    sp<InputChannel> serverChannel;
+    sp<InputChannel> clientChannel;
+    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
 
     if (result) {
         LOGE("Could not open input channel pair.  status=%d", result);
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 629c8fe..78137e2 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -36,11 +36,10 @@
     jmethodID obtain;
     jmethodID recycle;
 
-    jfieldID mDownTime;
-    jfieldID mEventTimeNano;
+    jfieldID mDownTimeNano;
     jfieldID mAction;
-    jfieldID mRawX;
-    jfieldID mRawY;
+    jfieldID mXOffset;
+    jfieldID mYOffset;
     jfieldID mXPrecision;
     jfieldID mYPrecision;
     jfieldID mDeviceId;
@@ -50,7 +49,9 @@
     jfieldID mNumSamples;
     jfieldID mPointerIdentifiers;
     jfieldID mDataSamples;
-    jfieldID mTimeSamples;
+    jfieldID mEventTimeNanoSamples;
+    jfieldID mLastDataSampleIndex;
+    jfieldID mLastEventTimeNanoSampleIndex;
 } gMotionEventClassInfo;
 
 // ----------------------------------------------------------------------------
@@ -69,22 +70,14 @@
         return NULL;
     }
 
-    // MotionEvent.mEventTimeNano is the time of the oldest sample because
-    // MotionEvent.addBatch does not update it as successive samples are added.
-    jlong eventTimeNano = numHistoricalSamples != 0
-            ? event->getHistoricalEventTime(0)
-            : event->getEventTime();
-
-    env->SetLongField(eventObj, gMotionEventClassInfo.mDownTime,
-            nanoseconds_to_milliseconds(event->getDownTime()));
-    env->SetLongField(eventObj, gMotionEventClassInfo.mEventTimeNano,
-            eventTimeNano);
+    env->SetLongField(eventObj, gMotionEventClassInfo.mDownTimeNano,
+            event->getDownTime());
     env->SetIntField(eventObj, gMotionEventClassInfo.mAction,
             event->getAction());
-    env->SetFloatField(eventObj, gMotionEventClassInfo.mRawX,
-            event->getRawX());
-    env->SetFloatField(eventObj, gMotionEventClassInfo.mRawY,
-            event->getRawY());
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mXOffset,
+            event->getXOffset());
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mYOffset,
+            event->getYOffset());
     env->SetFloatField(eventObj, gMotionEventClassInfo.mXPrecision,
             event->getXPrecision());
     env->SetFloatField(eventObj, gMotionEventClassInfo.mYPrecision,
@@ -99,65 +92,62 @@
             numPointers);
     env->SetIntField(eventObj, gMotionEventClassInfo.mNumSamples,
             numSamples);
+    env->SetIntField(eventObj, gMotionEventClassInfo.mLastDataSampleIndex,
+            (numSamples - 1) * numPointers * NUM_SAMPLE_DATA);
+    env->SetIntField(eventObj, gMotionEventClassInfo.mLastEventTimeNanoSampleIndex,
+            numSamples - 1);
 
     jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj,
             gMotionEventClassInfo.mPointerIdentifiers));
     jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj,
             gMotionEventClassInfo.mDataSamples));
-    jlongArray timeSampleArray = jlongArray(env->GetObjectField(eventObj,
-            gMotionEventClassInfo.mTimeSamples));
+    jlongArray eventTimeNanoSampleArray = jlongArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mEventTimeNanoSamples));
 
     jint* pointerIdentifiers = (jint*)env->GetPrimitiveArrayCritical(pointerIdentifierArray, NULL);
     jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL);
-    jlong* timeSamples = (jlong*)env->GetPrimitiveArrayCritical(timeSampleArray, NULL);
+    jlong* eventTimeNanoSamples = (jlong*)env->GetPrimitiveArrayCritical(
+            eventTimeNanoSampleArray, NULL);
 
+    const int32_t* srcPointerIdentifiers = event->getPointerIds();
+    jint* destPointerIdentifiers = pointerIdentifiers;
     for (jint i = 0; i < numPointers; i++) {
-        pointerIdentifiers[i] = event->getPointerId(i);
+        *(destPointerIdentifiers++) = *(srcPointerIdentifiers++);
     }
 
-    // Most recent data is in first slot of the DVM array, followed by the oldest,
-    // and then all others are in order.
-
-    jfloat* currentDataSample = dataSamples;
-    jlong* currentTimeSample = timeSamples;
-
-    *(currentTimeSample++) = nanoseconds_to_milliseconds(event->getEventTime());
-    for (jint j = 0; j < numPointers; j++) {
-        *(currentDataSample++) = event->getX(j);
-        *(currentDataSample++) = event->getY(j);
-        *(currentDataSample++) = event->getPressure(j);
-        *(currentDataSample++) = event->getSize(j);
+    const nsecs_t* srcSampleEventTimes = event->getSampleEventTimes();
+    jlong* destEventTimeNanoSamples = eventTimeNanoSamples;
+    for (jint i = 0; i < numSamples; i++) {
+        *(destEventTimeNanoSamples++) = *(srcSampleEventTimes++);
     }
 
-    for (jint i = 0; i < numHistoricalSamples; i++) {
-        *(currentTimeSample++) = nanoseconds_to_milliseconds(event->getHistoricalEventTime(i));
-        for (jint j = 0; j < numPointers; j++) {
-            *(currentDataSample++) = event->getHistoricalX(j, i);
-            *(currentDataSample++) = event->getHistoricalY(j, i);
-            *(currentDataSample++) = event->getHistoricalPressure(j, i);
-            *(currentDataSample++) = event->getHistoricalSize(j, i);
-        }
+    const PointerCoords* srcSamplePointerCoords = event->getSamplePointerCoords();
+    jfloat* destDataSamples = dataSamples;
+    jint numItems = numSamples * numPointers;
+    for (jint i = 0; i < numItems; i++) {
+        *(destDataSamples++) = srcSamplePointerCoords->x;
+        *(destDataSamples++) = srcSamplePointerCoords->y;
+        *(destDataSamples++) = srcSamplePointerCoords->pressure;
+        *(destDataSamples++) = srcSamplePointerCoords->size;
+        srcSamplePointerCoords += 1;
     }
 
     env->ReleasePrimitiveArrayCritical(pointerIdentifierArray, pointerIdentifiers, 0);
     env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, 0);
-    env->ReleasePrimitiveArrayCritical(timeSampleArray, timeSamples, 0);
+    env->ReleasePrimitiveArrayCritical(eventTimeNanoSampleArray, eventTimeNanoSamples, 0);
 
     env->DeleteLocalRef(pointerIdentifierArray);
     env->DeleteLocalRef(dataSampleArray);
-    env->DeleteLocalRef(timeSampleArray);
+    env->DeleteLocalRef(eventTimeNanoSampleArray);
     return eventObj;
 }
 
 void android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature,
         MotionEvent* event) {
-    // MotionEvent.mEventTimeNano is the time of the oldest sample because
-    // MotionEvent.addBatch does not update it as successive samples are added.
-    jlong downTime = env->GetLongField(eventObj, gMotionEventClassInfo.mDownTime);
-    jlong eventTimeNano = env->GetLongField(eventObj, gMotionEventClassInfo.mEventTimeNano);
+    jlong downTimeNano = env->GetLongField(eventObj, gMotionEventClassInfo.mDownTimeNano);
     jint action = env->GetIntField(eventObj, gMotionEventClassInfo.mAction);
-    jfloat rawX = env->GetFloatField(eventObj, gMotionEventClassInfo.mRawX);
-    jfloat rawY = env->GetFloatField(eventObj, gMotionEventClassInfo.mRawY);
+    jfloat xOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mXOffset);
+    jfloat yOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mYOffset);
     jfloat xPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mXPrecision);
     jfloat yPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mYPrecision);
     jint deviceId = env->GetIntField(eventObj, gMotionEventClassInfo.mDeviceId);
@@ -169,72 +159,51 @@
             gMotionEventClassInfo.mPointerIdentifiers));
     jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj,
             gMotionEventClassInfo.mDataSamples));
-    jlongArray timeSampleArray = jlongArray(env->GetObjectField(eventObj,
-            gMotionEventClassInfo.mTimeSamples));
+    jlongArray eventTimeNanoSampleArray = jlongArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mEventTimeNanoSamples));
 
     LOG_FATAL_IF(numPointers == 0, "numPointers was zero");
     LOG_FATAL_IF(numSamples == 0, "numSamples was zero");
 
     jint* pointerIdentifiers = (jint*)env->GetPrimitiveArrayCritical(pointerIdentifierArray, NULL);
     jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL);
-    jlong* timeSamples = (jlong*)env->GetPrimitiveArrayCritical(timeSampleArray, NULL);
+    jlong* eventTimeNanoSamples = (jlong*)env->GetPrimitiveArrayCritical(
+            eventTimeNanoSampleArray, NULL);
 
-    // Most recent data is in first slot of the DVM array, followed by the oldest,
-    // and then all others are in order.  eventTimeNano is the time of the oldest sample
-    // since MotionEvent.addBatch does not update it.
+    jfloat* srcDataSamples = dataSamples;
+    jlong* srcEventTimeNanoSamples = eventTimeNanoSamples;
 
-    jint numHistoricalSamples = numSamples - 1;
-    jint dataSampleStride = numPointers * NUM_SAMPLE_DATA;
-
-    const jfloat* currentDataSample;
-    const jlong* currentTimeSample;
-    if (numHistoricalSamples == 0) {
-        currentDataSample = dataSamples;
-        currentTimeSample = timeSamples;
-    } else {
-        currentDataSample = dataSamples + dataSampleStride;
-        currentTimeSample = timeSamples + 1;
-    }
-
-    PointerCoords pointerCoords[MAX_POINTERS];
+    jlong sampleEventTime = *(srcEventTimeNanoSamples++);
+    PointerCoords samplePointerCoords[MAX_POINTERS];
     for (jint j = 0; j < numPointers; j++) {
-        pointerCoords[j].x = *(currentDataSample++);
-        pointerCoords[j].y = *(currentDataSample++);
-        pointerCoords[j].pressure = *(currentDataSample++);
-        pointerCoords[j].size = *(currentDataSample++);
+        samplePointerCoords[j].x = *(srcDataSamples++);
+        samplePointerCoords[j].y = *(srcDataSamples++);
+        samplePointerCoords[j].pressure = *(srcDataSamples++);
+        samplePointerCoords[j].size = *(srcDataSamples++);
     }
 
     event->initialize(deviceId, nature, action, edgeFlags, metaState,
-            rawX, rawY, xPrecision, yPrecision,
-            milliseconds_to_nanoseconds(downTime), eventTimeNano,
-            numPointers, pointerIdentifiers, pointerCoords);
+            xOffset, yOffset, xPrecision, yPrecision, downTimeNano, sampleEventTime,
+            numPointers, pointerIdentifiers, samplePointerCoords);
 
-    while (numHistoricalSamples > 0) {
-        numHistoricalSamples -= 1;
-        if (numHistoricalSamples == 0) {
-            currentDataSample = dataSamples;
-            currentTimeSample = timeSamples;
-        }
-
-        nsecs_t sampleEventTime = milliseconds_to_nanoseconds(*(currentTimeSample++));
-
+    for (jint i = 1; i < numSamples; i++) {
+        sampleEventTime = *(srcEventTimeNanoSamples++);
         for (jint j = 0; j < numPointers; j++) {
-            pointerCoords[j].x = *(currentDataSample++);
-            pointerCoords[j].y = *(currentDataSample++);
-            pointerCoords[j].pressure = *(currentDataSample++);
-            pointerCoords[j].size = *(currentDataSample++);
+            samplePointerCoords[j].x = *(srcDataSamples++);
+            samplePointerCoords[j].y = *(srcDataSamples++);
+            samplePointerCoords[j].pressure = *(srcDataSamples++);
+            samplePointerCoords[j].size = *(srcDataSamples++);
         }
-
-        event->addSample(sampleEventTime, pointerCoords);
+        event->addSample(sampleEventTime, samplePointerCoords);
     }
 
     env->ReleasePrimitiveArrayCritical(pointerIdentifierArray, pointerIdentifiers, JNI_ABORT);
     env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, JNI_ABORT);
-    env->ReleasePrimitiveArrayCritical(timeSampleArray, timeSamples, JNI_ABORT);
+    env->ReleasePrimitiveArrayCritical(eventTimeNanoSampleArray, eventTimeNanoSamples, JNI_ABORT);
 
     env->DeleteLocalRef(pointerIdentifierArray);
     env->DeleteLocalRef(dataSampleArray);
-    env->DeleteLocalRef(timeSampleArray);
+    env->DeleteLocalRef(eventTimeNanoSampleArray);
 }
 
 void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj) {
@@ -273,16 +242,14 @@
     GET_METHOD_ID(gMotionEventClassInfo.recycle, gMotionEventClassInfo.clazz,
             "recycle", "()V");
 
-    GET_FIELD_ID(gMotionEventClassInfo.mDownTime, gMotionEventClassInfo.clazz,
-            "mDownTime", "J");
-    GET_FIELD_ID(gMotionEventClassInfo.mEventTimeNano, gMotionEventClassInfo.clazz,
-            "mEventTimeNano", "J");
+    GET_FIELD_ID(gMotionEventClassInfo.mDownTimeNano, gMotionEventClassInfo.clazz,
+            "mDownTimeNano", "J");
     GET_FIELD_ID(gMotionEventClassInfo.mAction, gMotionEventClassInfo.clazz,
             "mAction", "I");
-    GET_FIELD_ID(gMotionEventClassInfo.mRawX, gMotionEventClassInfo.clazz,
-            "mRawX", "F");
-    GET_FIELD_ID(gMotionEventClassInfo.mRawY, gMotionEventClassInfo.clazz,
-            "mRawY", "F");
+    GET_FIELD_ID(gMotionEventClassInfo.mXOffset, gMotionEventClassInfo.clazz,
+            "mXOffset", "F");
+    GET_FIELD_ID(gMotionEventClassInfo.mYOffset, gMotionEventClassInfo.clazz,
+            "mYOffset", "F");
     GET_FIELD_ID(gMotionEventClassInfo.mXPrecision, gMotionEventClassInfo.clazz,
             "mXPrecision", "F");
     GET_FIELD_ID(gMotionEventClassInfo.mYPrecision, gMotionEventClassInfo.clazz,
@@ -301,8 +268,12 @@
             "mPointerIdentifiers", "[I");
     GET_FIELD_ID(gMotionEventClassInfo.mDataSamples, gMotionEventClassInfo.clazz,
             "mDataSamples", "[F");
-    GET_FIELD_ID(gMotionEventClassInfo.mTimeSamples, gMotionEventClassInfo.clazz,
-            "mTimeSamples", "[J");
+    GET_FIELD_ID(gMotionEventClassInfo.mEventTimeNanoSamples, gMotionEventClassInfo.clazz,
+            "mEventTimeNanoSamples", "[J");
+    GET_FIELD_ID(gMotionEventClassInfo.mLastDataSampleIndex, gMotionEventClassInfo.clazz,
+            "mLastDataSampleIndex", "I");
+    GET_FIELD_ID(gMotionEventClassInfo.mLastEventTimeNanoSampleIndex, gMotionEventClassInfo.clazz,
+            "mLastEventTimeNanoSampleIndex", "I");
 
     return 0;
 }
diff --git a/include/ui/Input.h b/include/ui/Input.h
index d45bfcf..92ff872 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -148,6 +148,9 @@
     int32_t mNature;
 };
 
+/*
+ * Key events.
+ */
 class KeyEvent : public InputEvent {
 public:
     virtual ~KeyEvent() { }
@@ -193,6 +196,9 @@
     nsecs_t mEventTime;
 };
 
+/*
+ * Motion events.
+ */
 class MotionEvent : public InputEvent {
 public:
     virtual ~MotionEvent() { }
@@ -205,6 +211,10 @@
 
     inline int32_t getMetaState() const { return mMetaState; }
 
+    inline float getXOffset() const { return mXOffset; }
+
+    inline float getYOffset() const { return mYOffset; }
+
     inline float getXPrecision() const { return mXPrecision; }
 
     inline float getYPrecision() const { return mYPrecision; }
@@ -217,18 +227,22 @@
 
     inline nsecs_t getEventTime() const { return mSampleEventTimes[getHistorySize()]; }
 
-    inline float getRawX() const { return mRawX; }
-
-    inline float getRawY() const { return mRawY; }
-
-    inline float getX(size_t pointerIndex) const {
+    inline float getRawX(size_t pointerIndex) const {
         return getCurrentPointerCoords(pointerIndex).x;
     }
 
-    inline float getY(size_t pointerIndex) const {
+    inline float getRawY(size_t pointerIndex) const {
         return getCurrentPointerCoords(pointerIndex).y;
     }
 
+    inline float getX(size_t pointerIndex) const {
+        return getRawX(pointerIndex) + mXOffset;
+    }
+
+    inline float getY(size_t pointerIndex) const {
+        return getRawY(pointerIndex) + mYOffset;
+    }
+
     inline float getPressure(size_t pointerIndex) const {
         return getCurrentPointerCoords(pointerIndex).pressure;
     }
@@ -243,14 +257,22 @@
         return mSampleEventTimes[historicalIndex];
     }
 
-    inline float getHistoricalX(size_t pointerIndex, size_t historicalIndex) const {
+    inline float getHistoricalRawX(size_t pointerIndex, size_t historicalIndex) const {
         return getHistoricalPointerCoords(pointerIndex, historicalIndex).x;
     }
 
-    inline float getHistoricalY(size_t pointerIndex, size_t historicalIndex) const {
+    inline float getHistoricalRawY(size_t pointerIndex, size_t historicalIndex) const {
         return getHistoricalPointerCoords(pointerIndex, historicalIndex).y;
     }
 
+    inline float getHistoricalX(size_t pointerIndex, size_t historicalIndex) const {
+        return getHistoricalRawX(pointerIndex, historicalIndex) + mXOffset;
+    }
+
+    inline float getHistoricalY(size_t pointerIndex, size_t historicalIndex) const {
+        return getHistoricalRawY(pointerIndex, historicalIndex) + mYOffset;
+    }
+
     inline float getHistoricalPressure(size_t pointerIndex, size_t historicalIndex) const {
         return getHistoricalPointerCoords(pointerIndex, historicalIndex).pressure;
     }
@@ -265,8 +287,8 @@
             int32_t action,
             int32_t edgeFlags,
             int32_t metaState,
-            float rawX,
-            float rawY,
+            float xOffset,
+            float yOffset,
             float xPrecision,
             float yPrecision,
             nsecs_t downTime,
@@ -281,12 +303,19 @@
 
     void offsetLocation(float xOffset, float yOffset);
 
+    // Low-level accessors.
+    inline const int32_t* getPointerIds() const { return mPointerIds.array(); }
+    inline const nsecs_t* getSampleEventTimes() const { return mSampleEventTimes.array(); }
+    inline const PointerCoords* getSamplePointerCoords() const {
+            return mSamplePointerCoords.array();
+    }
+
 private:
     int32_t mAction;
     int32_t mEdgeFlags;
     int32_t mMetaState;
-    float mRawX;
-    float mRawY;
+    float mXOffset;
+    float mYOffset;
     float mXPrecision;
     float mYPrecision;
     nsecs_t mDownTime;
diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h
index 9537523..7b182f3 100644
--- a/include/ui/InputTransport.h
+++ b/include/ui/InputTransport.h
@@ -62,7 +62,7 @@
      * Returns OK on success.
      */
     static status_t openInputChannelPair(const String8& name,
-            InputChannel** outServerChannel, InputChannel** outClientChannel);
+            sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel);
 
     inline String8 getName() const { return mName; }
     inline int32_t getAshmemFd() const { return mAshmemFd; }
@@ -72,7 +72,8 @@
     /* Sends a signal to the other endpoint.
      *
      * Returns OK on success.
-     * Errors probably indicate that the channel is broken.
+     * Returns DEAD_OBJECT if the channel's peer has been closed.
+     * Other errors probably indicate that the channel is broken.
      */
     status_t sendSignal(char signal);
 
@@ -81,6 +82,7 @@
      *
      * Returns OK on success.
      * Returns WOULD_BLOCK if there is no signal present.
+     * Returns DEAD_OBJECT if the channel's peer has been closed.
      * Other errors probably indicate that the channel is broken.
      */
     status_t receiveSignal(char* outSignal);
@@ -298,7 +300,7 @@
      * Returns INVALID_OPERATION if there is no currently published event.
      * Returns NO_MEMORY if the event could not be created.
      */
-    status_t consume(InputEventFactoryInterface* factory, InputEvent** event);
+    status_t consume(InputEventFactoryInterface* factory, InputEvent** outEvent);
 
     /* Sends a finished signal to the publisher to inform it that the current message is
      * finished processing.
diff --git a/include/utils/PollLoop.h b/include/utils/PollLoop.h
index c9d951f..a95fb17 100644
--- a/include/utils/PollLoop.h
+++ b/include/utils/PollLoop.h
@@ -114,8 +114,10 @@
     };
 
     Mutex mLock;
-    Condition mAwake;
     bool mPolling;
+    uint32_t mWaiters;
+    Condition mAwake;
+    Condition mResume;
 
     int mWakeReadPipeFd;
     int mWakeWritePipeFd;
diff --git a/include/utils/Vector.h b/include/utils/Vector.h
index d40ae16..ec851bd 100644
--- a/include/utils/Vector.h
+++ b/include/utils/Vector.h
@@ -115,10 +115,10 @@
 
 
     //! insert an array at a given index
-            ssize_t         insertArrayAt(const TYPE* array, size_t index, size_t numItems);
+            ssize_t         insertArrayAt(const TYPE* array, size_t index, size_t length);
 
     //! append an array at the end of this vector
-            ssize_t         appendArray(const TYPE* array, size_t numItems);
+            ssize_t         appendArray(const TYPE* array, size_t length);
 
             /*! 
              * add/insert/replace items
@@ -126,7 +126,7 @@
              
     //! insert one or several items initialized with their default constructor
     inline  ssize_t         insertAt(size_t index, size_t numItems = 1);
-    //! insert on onr several items initialized from a prototype item
+    //! insert one or several items initialized from a prototype item
             ssize_t         insertAt(const TYPE& prototype_item, size_t index, size_t numItems = 1);
     //! pop the top of the stack (removes the last element). No-op if the stack's empty
     inline  void            pop();
@@ -265,13 +265,13 @@
 }
 
 template<class TYPE> inline
-ssize_t Vector<TYPE>::insertArrayAt(const TYPE* array, size_t index, size_t numItems) {
-    return VectorImpl::insertAt(array, index, numItems);
+ssize_t Vector<TYPE>::insertArrayAt(const TYPE* array, size_t index, size_t length) {
+    return VectorImpl::insertArrayAt(array, index, length);
 }
 
 template<class TYPE> inline
-ssize_t Vector<TYPE>::appendArray(const TYPE* array, size_t numItems) {
-    return VectorImpl::add(array, numItems);
+ssize_t Vector<TYPE>::appendArray(const TYPE* array, size_t length) {
+    return VectorImpl::appendArray(array, length);
 }
 
 template<class TYPE> inline
diff --git a/include/utils/VectorImpl.h b/include/utils/VectorImpl.h
index 46a7bc2..c4ec2ff 100644
--- a/include/utils/VectorImpl.h
+++ b/include/utils/VectorImpl.h
@@ -65,9 +65,11 @@
             size_t          capacity() const;
             ssize_t         setCapacity(size_t size);
 
-            /*! append/insert another vector */
+            /*! append/insert another vector or array */
             ssize_t         insertVectorAt(const VectorImpl& vector, size_t index);
             ssize_t         appendVector(const VectorImpl& vector);
+            ssize_t         insertArrayAt(const void* array, size_t index, size_t length);
+            ssize_t         appendArray(const void* array, size_t length);
             
             /*! add/insert/replace items */
             ssize_t         insertAt(size_t where, size_t numItems = 1);
@@ -76,7 +78,7 @@
             void            push();
             void            push(const void* item);
             ssize_t         add();
-            ssize_t         add(const void* item, size_t numItems = 1);
+            ssize_t         add(const void* item);
             ssize_t         replaceAt(size_t index);
             ssize_t         replaceAt(const void* item, size_t index);
 
@@ -184,8 +186,8 @@
             void            push(const void* item);
             ssize_t         insertVectorAt(const VectorImpl& vector, size_t index);
             ssize_t         appendVector(const VectorImpl& vector);
-            ssize_t         insertArrayAt(const void* array, size_t index, size_t numItems);
-            ssize_t         appendArray(const void* array, size_t numItems);
+            ssize_t         insertArrayAt(const void* array, size_t index, size_t length);
+            ssize_t         appendArray(const void* array, size_t length);
             ssize_t         insertAt(size_t where, size_t numItems = 1);
             ssize_t         insertAt(const void* item, size_t where, size_t numItems = 1);
             ssize_t         replaceAt(size_t index);
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index d367708..0e6f2f5 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -50,8 +50,8 @@
         int32_t action,
         int32_t edgeFlags,
         int32_t metaState,
-        float rawX,
-        float rawY,
+        float xOffset,
+        float yOffset,
         float xPrecision,
         float yPrecision,
         nsecs_t downTime,
@@ -63,8 +63,8 @@
     mAction = action;
     mEdgeFlags = edgeFlags;
     mMetaState = metaState;
-    mRawX = rawX;
-    mRawY = rawY;
+    mXOffset = xOffset;
+    mYOffset = yOffset;
     mXPrecision = xPrecision;
     mYPrecision = yPrecision;
     mDownTime = downTime;
@@ -83,13 +83,8 @@
 }
 
 void MotionEvent::offsetLocation(float xOffset, float yOffset) {
-    if (xOffset != 0 || yOffset != 0) {
-        for (size_t i = 0; i < mSamplePointerCoords.size(); i++) {
-            PointerCoords& pointerCoords = mSamplePointerCoords.editItemAt(i);
-            pointerCoords.x += xOffset;
-            pointerCoords.y += yOffset;
-        }
-    }
+    mXOffset += xOffset;
+    mYOffset += yOffset;
 }
 
 } // namespace android
@@ -163,6 +158,14 @@
     return reinterpret_cast<const MotionEvent*>(motion_event)->getEventTime();
 }
 
+float motion_event_get_x_offset(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getXOffset();
+}
+
+float motion_event_get_y_offset(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getYOffset();
+}
+
 float motion_event_get_x_precision(const input_event_t* motion_event) {
     return reinterpret_cast<const MotionEvent*>(motion_event)->getXPrecision();
 }
@@ -179,12 +182,12 @@
     return reinterpret_cast<const MotionEvent*>(motion_event)->getPointerId(pointer_index);
 }
 
-float motion_event_get_raw_x(const input_event_t* motion_event) {
-    return reinterpret_cast<const MotionEvent*>(motion_event)->getRawX();
+float motion_event_get_raw_x(const input_event_t* motion_event, size_t pointer_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getRawX(pointer_index);
 }
 
-float motion_event_get_raw_y(const input_event_t* motion_event) {
-    return reinterpret_cast<const MotionEvent*>(motion_event)->getRawY();
+float motion_event_get_raw_y(const input_event_t* motion_event, size_t pointer_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getRawY(pointer_index);
 }
 
 float motion_event_get_x(const input_event_t* motion_event, size_t pointer_index) {
@@ -213,6 +216,18 @@
             history_index);
 }
 
+float motion_event_get_historical_raw_x(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalRawX(
+            pointer_index, history_index);
+}
+
+float motion_event_get_historical_raw_y(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalRawY(
+            pointer_index, history_index);
+}
+
 float motion_event_get_historical_x(input_event_t* motion_event, size_t pointer_index,
         size_t history_index) {
     return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalX(
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index f058271..14dcada 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -379,8 +379,7 @@
 
     mReusableMotionEvent.initialize(entry->deviceId, entry->nature, entry->action,
             entry->edgeFlags, entry->metaState,
-            entry->firstSample.pointerCoords[0].x, entry->firstSample.pointerCoords[0].y,
-            entry->xPrecision, entry->yPrecision,
+            0, 0, entry->xPrecision, entry->yPrecision,
             entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
             entry->firstSample.pointerCoords);
 
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 62b5f28..5a280ae 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -19,6 +19,9 @@
 // Log debug messages about pointers.
 #define DEBUG_POINTERS 1
 
+// Log debug messages about pointer assignment calculations.
+#define DEBUG_POINTER_ASSIGNMENT 0
+
 #include <cutils/log.h>
 #include <ui/InputReader.h>
 
@@ -57,6 +60,14 @@
     return a < b ? a : b;
 }
 
+template<typename T>
+inline static void swap(T& a, T& b) {
+    T temp = a;
+    a = b;
+    b = temp;
+}
+
+
 int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
     int32_t mask;
     switch (keyCode) {
@@ -188,6 +199,12 @@
     jumpyTouchFilter.jumpyPointsDropped = 0;
 }
 
+struct PointerDistanceHeapElement {
+    uint32_t currentPointerIndex : 8;
+    uint32_t lastPointerIndex : 8;
+    uint64_t distance : 48; // squared distance
+};
+
 void InputDevice::TouchScreenState::calculatePointerIds() {
     uint32_t currentPointerCount = currentTouch.pointerCount;
     uint32_t lastPointerCount = lastTouch.pointerCount;
@@ -214,11 +231,7 @@
         // We build a heap of squared euclidean distances between current and last pointers
         // associated with the current and last pointer indices.  Then, we find the best
         // match (by distance) for each current pointer.
-        struct {
-            uint32_t currentPointerIndex : 8;
-            uint32_t lastPointerIndex : 8;
-            uint64_t distance : 48; // squared distance
-        } heap[MAX_POINTERS * MAX_POINTERS];
+        PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS];
 
         uint32_t heapSize = 0;
         for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
@@ -233,23 +246,45 @@
                 uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
 
                 // Insert new element into the heap (sift up).
+                heap[heapSize].currentPointerIndex = currentPointerIndex;
+                heap[heapSize].lastPointerIndex = lastPointerIndex;
+                heap[heapSize].distance = distance;
                 heapSize += 1;
-                uint32_t insertionIndex = heapSize;
-                while (insertionIndex > 1) {
-                    uint32_t parentIndex = (insertionIndex - 1) / 2;
-                    if (distance < heap[parentIndex].distance) {
-                        heap[insertionIndex] = heap[parentIndex];
-                        insertionIndex = parentIndex;
-                    } else {
-                        break;
-                    }
-                }
-                heap[insertionIndex].currentPointerIndex = currentPointerIndex;
-                heap[insertionIndex].lastPointerIndex = lastPointerIndex;
-                heap[insertionIndex].distance = distance;
             }
         }
 
+        // Heapify
+        for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) {
+            startIndex -= 1;
+            for (uint32_t parentIndex = startIndex; ;) {
+                uint32_t childIndex = parentIndex * 2 + 1;
+                if (childIndex >= heapSize) {
+                    break;
+                }
+
+                if (childIndex + 1 < heapSize
+                        && heap[childIndex + 1].distance < heap[childIndex].distance) {
+                    childIndex += 1;
+                }
+
+                if (heap[parentIndex].distance <= heap[childIndex].distance) {
+                    break;
+                }
+
+                swap(heap[parentIndex], heap[childIndex]);
+                parentIndex = childIndex;
+            }
+        }
+
+#if DEBUG_POINTER_ASSIGNMENT
+        LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize);
+        for (size_t i = 0; i < heapSize; i++) {
+            LOGD("  heap[%d]: cur=%d, last=%d, distance=%lld",
+                    i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+                    heap[i].distance);
+        }
+#endif
+
         // Pull matches out by increasing order of distance.
         // To avoid reassigning pointers that have already been matched, the loop keeps track
         // of which last and current pointers have been matched using the matchedXXXBits variables.
@@ -262,7 +297,7 @@
             for (;;) {
                 if (first) {
                     // The first time through the loop, we just consume the root element of
-                    // the heap (the one with smalled distance).
+                    // the heap (the one with smallest distance).
                     first = false;
                 } else {
                     // Previous iterations consumed the root element of the heap.
@@ -270,10 +305,10 @@
                     heapSize -= 1;
                     assert(heapSize > 0);
 
-                    // Sift down to find where the element at index heapSize needs to be moved.
-                    uint32_t rootIndex = 0;
-                    for (;;) {
-                        uint32_t childIndex = rootIndex * 2 + 1;
+                    // Sift down.
+                    heap[0] = heap[heapSize];
+                    for (uint32_t parentIndex = 0; ;) {
+                        uint32_t childIndex = parentIndex * 2 + 1;
                         if (childIndex >= heapSize) {
                             break;
                         }
@@ -283,14 +318,22 @@
                             childIndex += 1;
                         }
 
-                        if (heap[heapSize].distance < heap[childIndex].distance) {
+                        if (heap[parentIndex].distance <= heap[childIndex].distance) {
                             break;
                         }
 
-                        heap[rootIndex] = heap[childIndex];
-                        rootIndex = childIndex;
+                        swap(heap[parentIndex], heap[childIndex]);
+                        parentIndex = childIndex;
                     }
-                    heap[rootIndex] = heap[heapSize];
+
+#if DEBUG_POINTER_ASSIGNMENT
+                    LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize);
+                    for (size_t i = 0; i < heapSize; i++) {
+                        LOGD("  heap[%d]: cur=%d, last=%d, distance=%lld",
+                                i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+                                heap[i].distance);
+                    }
+#endif
                 }
 
                 uint32_t currentPointerIndex = heap[0].currentPointerIndex;
@@ -306,6 +349,11 @@
                 currentTouch.pointers[currentPointerIndex].id = id;
                 currentTouch.idToIndex[id] = currentPointerIndex;
                 usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+                LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld",
+                        lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+#endif
                 break;
             }
         }
@@ -320,6 +368,11 @@
                 currentTouch.idToIndex[id] = currentPointerIndex;
                 usedIdBits.markBit(id);
 
+#if DEBUG_POINTER_ASSIGNMENT
+                LOGD("calculatePointerIds - assigned: cur=%d, id=%d",
+                        currentPointerIndex, id);
+#endif
+
                 if (--i == 0) break; // done
                 matchedCurrentBits.markBit(currentPointerIndex);
             }
@@ -1208,8 +1261,10 @@
 
         int32_t x = device->touchScreen.currentTouch.pointers[0].x;
         int32_t y = device->touchScreen.currentTouch.pointers[0].y;
-        if (device->touchScreen.isPointInsideDisplay(x, y)) {
-            // Pointer moved inside the display area.  Send key cancellation.
+        if (device->touchScreen.isPointInsideDisplay(x, y)
+                || device->touchScreen.currentTouch.pointerCount != 1) {
+            // Pointer moved inside the display area or another pointer also went down.
+            // Send key cancellation.
             device->touchScreen.currentVirtualKey.down = false;
 
 #if DEBUG_VIRTUAL_KEYS
@@ -1227,7 +1282,7 @@
             device->touchScreen.lastTouch.clear();
             return false; // not consumed
         }
-    } else if (device->touchScreen.currentTouch.pointerCount > 0
+    } else if (device->touchScreen.currentTouch.pointerCount == 1
             && device->touchScreen.lastTouch.pointerCount == 0) {
         int32_t x = device->touchScreen.currentTouch.pointers[0].x;
         int32_t y = device->touchScreen.currentTouch.pointers[0].y;
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index a24180f..86bbd37 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -8,13 +8,13 @@
 //#define LOG_NDEBUG 0
 
 // Log debug messages about channel signalling (send signal, receive signal)
-#define DEBUG_CHANNEL_SIGNALS 1
+#define DEBUG_CHANNEL_SIGNALS 0
 
 // Log debug messages whenever InputChannel objects are created/destroyed
-#define DEBUG_CHANNEL_LIFECYCLE 1
+#define DEBUG_CHANNEL_LIFECYCLE 0
 
 // Log debug messages about transport actions (initialize, reset, publish, ...)
-#define DEBUG_TRANSPORT_ACTIONS 1
+#define DEBUG_TRANSPORT_ACTIONS 0
 
 
 #include <cutils/ashmem.h>
@@ -70,7 +70,7 @@
 }
 
 status_t InputChannel::openInputChannelPair(const String8& name,
-        InputChannel** outServerChannel, InputChannel** outClientChannel) {
+        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
     status_t result;
 
     int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE);
@@ -107,12 +107,12 @@
                     } else {
                         String8 serverChannelName = name;
                         serverChannelName.append(" (server)");
-                        *outServerChannel = new InputChannel(serverChannelName,
+                        outServerChannel = new InputChannel(serverChannelName,
                                 serverAshmemFd, reverse[0], forward[1]);
 
                         String8 clientChannelName = name;
                         clientChannelName.append(" (client)");
-                        *outClientChannel = new InputChannel(clientChannelName,
+                        outClientChannel = new InputChannel(clientChannelName,
                                 clientAshmemFd, forward[0], reverse[1]);
                         return OK;
                     }
@@ -125,8 +125,8 @@
         ::close(serverAshmemFd);
     }
 
-    *outServerChannel = NULL;
-    *outClientChannel = NULL;
+    outServerChannel.clear();
+    outClientChannel.clear();
     return result;
 }
 
@@ -155,6 +155,13 @@
         return OK;
     }
 
+    if (nRead == 0) { // check for EOF
+#if DEBUG_CHANNEL_SIGNALS
+        LOGD("channel '%s' ~ receive signal failed because peer was closed", mName.string());
+#endif
+        return DEAD_OBJECT;
+    }
+
     if (errno == EAGAIN) {
 #if DEBUG_CHANNEL_SIGNALS
         LOGD("channel '%s' ~ receive signal failed because no signal available", mName.string());
@@ -535,13 +542,13 @@
     return OK;
 }
 
-status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** event) {
+status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) {
 #if DEBUG_TRANSPORT_ACTIONS
     LOGD("channel '%s' consumer ~ consume",
             mChannel->getName().string());
 #endif
 
-    *event = NULL;
+    *outEvent = NULL;
 
     int ashmemFd = mChannel->getAshmemFd();
     int result = ashmem_pin_region(ashmemFd, 0, 0);
@@ -583,7 +590,7 @@
 
         populateKeyEvent(keyEvent);
 
-        *event = keyEvent;
+        *outEvent = keyEvent;
         break;
     }
 
@@ -593,7 +600,7 @@
 
         populateMotionEvent(motionEvent);
 
-        *event = motionEvent;
+        *outEvent = motionEvent;
         break;
     }
 
@@ -655,8 +662,8 @@
             mSharedMessage->motion.action,
             mSharedMessage->motion.edgeFlags,
             mSharedMessage->motion.metaState,
-            mSharedMessage->motion.sampleData[0].coords[0].x,
-            mSharedMessage->motion.sampleData[0].coords[0].y,
+            mSharedMessage->motion.xOffset,
+            mSharedMessage->motion.yOffset,
             mSharedMessage->motion.xPrecision,
             mSharedMessage->motion.yPrecision,
             mSharedMessage->motion.downTime,
@@ -676,9 +683,6 @@
             motionEvent->addSample(sampleData->eventTime, sampleData->coords);
         }
     }
-
-    motionEvent->offsetLocation(mSharedMessage->motion.xOffset,
-            mSharedMessage->motion.yOffset);
 }
 
 } // namespace android
diff --git a/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk
index 1ff896b..46d7493 100644
--- a/libs/ui/tests/Android.mk
+++ b/libs/ui/tests/Android.mk
@@ -3,7 +3,9 @@
 include $(CLEAR_VARS)
 
 test_src_files := \
-    InputDispatcher_test.cpp
+    InputChannel_test.cpp \
+    InputDispatcher_test.cpp \
+    InputPublisherAndConsumer_test.cpp
 
 shared_libraries := \
 	libcutils \
diff --git a/libs/ui/tests/InputChannel_test.cpp b/libs/ui/tests/InputChannel_test.cpp
new file mode 100644
index 0000000..6cec1c0
--- /dev/null
+++ b/libs/ui/tests/InputChannel_test.cpp
@@ -0,0 +1,158 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include <ui/InputTransport.h>
+#include <utils/Timers.h>
+#include <utils/StopWatch.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <cutils/ashmem.h>
+
+#include "../../utils/tests/TestHelpers.h"
+
+namespace android {
+
+class InputChannelTest : public testing::Test {
+protected:
+    virtual void SetUp() { }
+    virtual void TearDown() { }
+};
+
+
+TEST_F(InputChannelTest, ConstructorAndDestructor_TakesOwnershipOfFileDescriptors) {
+    // Our purpose here is to verify that the input channel destructor closes the
+    // file descriptors provided to it.  One easy way is to provide it with one end
+    // of a pipe and to check for EPIPE on the other end after the channel is destroyed.
+    Pipe fakeAshmem, sendPipe, receivePipe;
+
+    sp<InputChannel> inputChannel = new InputChannel(String8("channel name"),
+            fakeAshmem.sendFd, receivePipe.receiveFd, sendPipe.sendFd);
+
+    EXPECT_STREQ("channel name", inputChannel->getName().string())
+            << "channel should have provided name";
+    EXPECT_EQ(fakeAshmem.sendFd, inputChannel->getAshmemFd())
+            << "channel should have provided ashmem fd";
+    EXPECT_EQ(receivePipe.receiveFd, inputChannel->getReceivePipeFd())
+            << "channel should have provided receive pipe fd";
+    EXPECT_EQ(sendPipe.sendFd, inputChannel->getSendPipeFd())
+            << "channel should have provided send pipe fd";
+
+    inputChannel.clear(); // destroys input channel
+
+    EXPECT_EQ(-EPIPE, fakeAshmem.readSignal())
+            << "channel should have closed ashmem fd when destroyed";
+    EXPECT_EQ(-EPIPE, receivePipe.writeSignal())
+            << "channel should have closed receive pipe fd when destroyed";
+    EXPECT_EQ(-EPIPE, sendPipe.readSignal())
+            << "channel should have closed send pipe fd when destroyed";
+
+    // clean up fds of Pipe endpoints that were closed so we don't try to close them again
+    fakeAshmem.sendFd = -1;
+    receivePipe.receiveFd = -1;
+    sendPipe.sendFd = -1;
+}
+
+TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) {
+    sp<InputChannel> serverChannel, clientChannel;
+
+    status_t result = InputChannel::openInputChannelPair(String8("channel name"),
+            serverChannel, clientChannel);
+
+    ASSERT_EQ(OK, result)
+            << "should have successfully opened a channel pair";
+
+    // Name
+    EXPECT_STREQ("channel name (server)", serverChannel->getName().string())
+            << "server channel should have suffixed name";
+    EXPECT_STREQ("channel name (client)", clientChannel->getName().string())
+            << "client channel should have suffixed name";
+
+    // Ashmem uniqueness
+    EXPECT_NE(serverChannel->getAshmemFd(), clientChannel->getAshmemFd())
+            << "server and client channel should have different ashmem fds because it was dup'd";
+
+    // Ashmem usability
+    ssize_t serverAshmemSize = ashmem_get_size_region(serverChannel->getAshmemFd());
+    ssize_t clientAshmemSize = ashmem_get_size_region(clientChannel->getAshmemFd());
+    uint32_t* serverAshmem = static_cast<uint32_t*>(mmap(NULL, serverAshmemSize,
+            PROT_READ | PROT_WRITE, MAP_SHARED, serverChannel->getAshmemFd(), 0));
+    uint32_t* clientAshmem = static_cast<uint32_t*>(mmap(NULL, clientAshmemSize,
+            PROT_READ | PROT_WRITE, MAP_SHARED, clientChannel->getAshmemFd(), 0));
+    ASSERT_TRUE(serverAshmem != NULL)
+            << "server channel ashmem should be mappable";
+    ASSERT_TRUE(clientAshmem != NULL)
+            << "client channel ashmem should be mappable";
+    *serverAshmem = 0xf00dd00d;
+    EXPECT_EQ(0xf00dd00d, *clientAshmem)
+            << "ashmem buffer should be shared by client and server";
+    munmap(serverAshmem, serverAshmemSize);
+    munmap(clientAshmem, clientAshmemSize);
+
+    // Server->Client communication
+    EXPECT_EQ(OK, serverChannel->sendSignal('S'))
+            << "server channel should be able to send signal to client channel";
+    char signal;
+    EXPECT_EQ(OK, clientChannel->receiveSignal(& signal))
+            << "client channel should be able to receive signal from server channel";
+    EXPECT_EQ('S', signal)
+            << "client channel should receive the correct signal from server channel";
+
+    // Client->Server communication
+    EXPECT_EQ(OK, clientChannel->sendSignal('c'))
+            << "client channel should be able to send signal to server channel";
+    EXPECT_EQ(OK, serverChannel->receiveSignal(& signal))
+            << "server channel should be able to receive signal from client channel";
+    EXPECT_EQ('c', signal)
+            << "server channel should receive the correct signal from client channel";
+}
+
+TEST_F(InputChannelTest, ReceiveSignal_WhenNoSignalPresent_ReturnsAnError) {
+    sp<InputChannel> serverChannel, clientChannel;
+
+    status_t result = InputChannel::openInputChannelPair(String8("channel name"),
+            serverChannel, clientChannel);
+
+    ASSERT_EQ(OK, result)
+            << "should have successfully opened a channel pair";
+
+    char signal;
+    EXPECT_EQ(WOULD_BLOCK, clientChannel->receiveSignal(& signal))
+            << "receiveSignal should have returned WOULD_BLOCK";
+}
+
+TEST_F(InputChannelTest, ReceiveSignal_WhenPeerClosed_ReturnsAnError) {
+    sp<InputChannel> serverChannel, clientChannel;
+
+    status_t result = InputChannel::openInputChannelPair(String8("channel name"),
+            serverChannel, clientChannel);
+
+    ASSERT_EQ(OK, result)
+            << "should have successfully opened a channel pair";
+
+    serverChannel.clear(); // close server channel
+
+    char signal;
+    EXPECT_EQ(DEAD_OBJECT, clientChannel->receiveSignal(& signal))
+            << "receiveSignal should have returned DEAD_OBJECT";
+}
+
+TEST_F(InputChannelTest, SendSignal_WhenPeerClosed_ReturnsAnError) {
+    sp<InputChannel> serverChannel, clientChannel;
+
+    status_t result = InputChannel::openInputChannelPair(String8("channel name"),
+            serverChannel, clientChannel);
+
+    ASSERT_EQ(OK, result)
+            << "should have successfully opened a channel pair";
+
+    serverChannel.clear(); // close server channel
+
+    EXPECT_EQ(DEAD_OBJECT, clientChannel->sendSignal('S'))
+            << "sendSignal should have returned DEAD_OBJECT";
+}
+
+
+} // namespace android
diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp
index 3d92043..1dc6e46 100644
--- a/libs/ui/tests/InputDispatcher_test.cpp
+++ b/libs/ui/tests/InputDispatcher_test.cpp
@@ -12,8 +12,7 @@
 };
 
 TEST_F(InputDispatcherTest, Dummy) {
-    SCOPED_TRACE("Trace");
-    ASSERT_FALSE(true);
+    // TODO
 }
 
 } // namespace android
diff --git a/libs/ui/tests/InputPublisherAndConsumer_test.cpp b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
new file mode 100644
index 0000000..2d6b531
--- /dev/null
+++ b/libs/ui/tests/InputPublisherAndConsumer_test.cpp
@@ -0,0 +1,449 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include <ui/InputTransport.h>
+#include <utils/Timers.h>
+#include <utils/StopWatch.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <cutils/ashmem.h>
+
+#include "../../utils/tests/TestHelpers.h"
+
+namespace android {
+
+class InputPublisherAndConsumerTest : public testing::Test {
+protected:
+    sp<InputChannel> serverChannel, clientChannel;
+    InputPublisher* mPublisher;
+    InputConsumer* mConsumer;
+    PreallocatedInputEventFactory mEventFactory;
+
+    virtual void SetUp() {
+        status_t result = InputChannel::openInputChannelPair(String8("channel name"),
+                serverChannel, clientChannel);
+
+        mPublisher = new InputPublisher(serverChannel);
+        mConsumer = new InputConsumer(clientChannel);
+    }
+
+    virtual void TearDown() {
+        if (mPublisher) {
+            delete mPublisher;
+            mPublisher = NULL;
+        }
+
+        if (mConsumer) {
+            delete mConsumer;
+            mConsumer = NULL;
+        }
+
+        serverChannel.clear();
+        clientChannel.clear();
+    }
+
+    void Initialize();
+    void PublishAndConsumeKeyEvent();
+    void PublishAndConsumeMotionEvent(
+            size_t samplesToAppendBeforeDispatch = 0,
+            size_t samplesToAppendAfterDispatch = 0);
+};
+
+TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
+    EXPECT_EQ(serverChannel.get(), mPublisher->getChannel().get());
+    EXPECT_EQ(clientChannel.get(), mConsumer->getChannel().get());
+}
+
+void InputPublisherAndConsumerTest::Initialize() {
+    status_t status;
+
+    status = mPublisher->initialize();
+    ASSERT_EQ(OK, status)
+            << "publisher initialize should return OK";
+
+    status = mConsumer->initialize();
+    ASSERT_EQ(OK, status)
+            << "consumer initialize should return OK";
+}
+
+void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
+    status_t status;
+
+    const int32_t deviceId = 1;
+    const int32_t nature = INPUT_EVENT_NATURE_KEY;
+    const int32_t action = KEY_EVENT_ACTION_DOWN;
+    const int32_t flags = KEY_EVENT_FLAG_FROM_SYSTEM;
+    const int32_t keyCode = KEYCODE_ENTER;
+    const int32_t scanCode = 13;
+    const int32_t metaState = META_ALT_LEFT_ON | META_ALT_ON;
+    const int32_t repeatCount = 1;
+    const nsecs_t downTime = 3;
+    const nsecs_t eventTime = 4;
+
+    status = mPublisher->publishKeyEvent(deviceId, nature, action, flags,
+            keyCode, scanCode, metaState, repeatCount, downTime, eventTime);
+    ASSERT_EQ(OK, status)
+            << "publisher publishKeyEvent should return OK";
+
+    status = mPublisher->sendDispatchSignal();
+    ASSERT_EQ(OK, status)
+            << "publisher sendDispatchSignal should return OK";
+
+    status = mConsumer->receiveDispatchSignal();
+    ASSERT_EQ(OK, status)
+            << "consumer receiveDispatchSignal should return OK";
+
+    InputEvent* event;
+    status = mConsumer->consume(& mEventFactory, & event);
+    ASSERT_EQ(OK, status)
+            << "consumer consume should return OK";
+
+    ASSERT_TRUE(event != NULL)
+            << "consumer should have returned non-NULL event";
+    ASSERT_EQ(INPUT_EVENT_TYPE_KEY, event->getType())
+            << "consumer should have returned a key event";
+
+    KeyEvent* keyEvent = static_cast<KeyEvent*>(event);
+    EXPECT_EQ(deviceId, keyEvent->getDeviceId());
+    EXPECT_EQ(nature, keyEvent->getNature());
+    EXPECT_EQ(action, keyEvent->getAction());
+    EXPECT_EQ(flags, keyEvent->getFlags());
+    EXPECT_EQ(keyCode, keyEvent->getKeyCode());
+    EXPECT_EQ(scanCode, keyEvent->getScanCode());
+    EXPECT_EQ(metaState, keyEvent->getMetaState());
+    EXPECT_EQ(repeatCount, keyEvent->getRepeatCount());
+    EXPECT_EQ(downTime, keyEvent->getDownTime());
+    EXPECT_EQ(eventTime, keyEvent->getEventTime());
+
+    status = mConsumer->sendFinishedSignal();
+    ASSERT_EQ(OK, status)
+            << "consumer sendFinishedSignal should return OK";
+
+    status = mPublisher->receiveFinishedSignal();
+    ASSERT_EQ(OK, status)
+            << "publisher receiveFinishedSignal should return OK";
+
+    status = mPublisher->reset();
+    ASSERT_EQ(OK, status)
+            << "publisher reset should return OK";
+}
+
+void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent(
+        size_t samplesToAppendBeforeDispatch, size_t samplesToAppendAfterDispatch) {
+    status_t status;
+
+    const int32_t deviceId = 1;
+    const int32_t nature = INPUT_EVENT_NATURE_TOUCH;
+    const int32_t action = MOTION_EVENT_ACTION_MOVE;
+    const int32_t edgeFlags = MOTION_EVENT_EDGE_FLAG_TOP;
+    const int32_t metaState = META_ALT_LEFT_ON | META_ALT_ON;
+    const float xOffset = -10;
+    const float yOffset = -20;
+    const float xPrecision = 0.25;
+    const float yPrecision = 0.5;
+    const nsecs_t downTime = 3;
+    const size_t pointerCount = 3;
+    const int32_t pointerIds[pointerCount] = { 2, 0, 1 };
+
+    Vector<nsecs_t> sampleEventTimes;
+    Vector<PointerCoords> samplePointerCoords;
+
+    for (size_t i = 0; i <= samplesToAppendAfterDispatch + samplesToAppendBeforeDispatch; i++) {
+        sampleEventTimes.push(i + 10);
+        for (size_t j = 0; j < pointerCount; j++) {
+            samplePointerCoords.push();
+            samplePointerCoords.editTop().x = 100 * i + j;
+            samplePointerCoords.editTop().y = 200 * i + j;
+            samplePointerCoords.editTop().pressure = 0.5 * i + j;
+            samplePointerCoords.editTop().size = 0.7 * i + j;
+        }
+    }
+
+    status = mPublisher->publishMotionEvent(deviceId, nature, action, edgeFlags,
+            metaState, xOffset, yOffset, xPrecision, yPrecision,
+            downTime, sampleEventTimes[0], pointerCount, pointerIds, samplePointerCoords.array());
+    ASSERT_EQ(OK, status)
+            << "publisher publishMotionEvent should return OK";
+
+    for (size_t i = 0; i < samplesToAppendBeforeDispatch; i++) {
+        size_t sampleIndex = i + 1;
+        status = mPublisher->appendMotionSample(sampleEventTimes[sampleIndex],
+                samplePointerCoords.array() + sampleIndex * pointerCount);
+        ASSERT_EQ(OK, status)
+                << "publisher appendMotionEvent should return OK";
+    }
+
+    status = mPublisher->sendDispatchSignal();
+    ASSERT_EQ(OK, status)
+            << "publisher sendDispatchSignal should return OK";
+
+    for (size_t i = 0; i < samplesToAppendAfterDispatch; i++) {
+        size_t sampleIndex = i + 1 + samplesToAppendBeforeDispatch;
+        status = mPublisher->appendMotionSample(sampleEventTimes[sampleIndex],
+                samplePointerCoords.array() + sampleIndex * pointerCount);
+        ASSERT_EQ(OK, status)
+                << "publisher appendMotionEvent should return OK";
+    }
+
+    status = mConsumer->receiveDispatchSignal();
+    ASSERT_EQ(OK, status)
+            << "consumer receiveDispatchSignal should return OK";
+
+    InputEvent* event;
+    status = mConsumer->consume(& mEventFactory, & event);
+    ASSERT_EQ(OK, status)
+            << "consumer consume should return OK";
+
+    ASSERT_TRUE(event != NULL)
+            << "consumer should have returned non-NULL event";
+    ASSERT_EQ(INPUT_EVENT_TYPE_MOTION, event->getType())
+            << "consumer should have returned a motion event";
+
+    size_t lastSampleIndex = samplesToAppendBeforeDispatch + samplesToAppendAfterDispatch;
+
+    MotionEvent* motionEvent = static_cast<MotionEvent*>(event);
+    EXPECT_EQ(deviceId, motionEvent->getDeviceId());
+    EXPECT_EQ(nature, motionEvent->getNature());
+    EXPECT_EQ(action, motionEvent->getAction());
+    EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags());
+    EXPECT_EQ(metaState, motionEvent->getMetaState());
+    EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
+    EXPECT_EQ(yPrecision, motionEvent->getYPrecision());
+    EXPECT_EQ(downTime, motionEvent->getDownTime());
+    EXPECT_EQ(sampleEventTimes[lastSampleIndex], motionEvent->getEventTime());
+    EXPECT_EQ(pointerCount, motionEvent->getPointerCount());
+    EXPECT_EQ(lastSampleIndex, motionEvent->getHistorySize());
+
+    for (size_t i = 0; i < pointerCount; i++) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(pointerIds[i], motionEvent->getPointerId(i));
+    }
+
+    for (size_t sampleIndex = 0; sampleIndex < lastSampleIndex; sampleIndex++) {
+        SCOPED_TRACE(sampleIndex);
+        EXPECT_EQ(sampleEventTimes[sampleIndex],
+                motionEvent->getHistoricalEventTime(sampleIndex));
+        for (size_t i = 0; i < pointerCount; i++) {
+            SCOPED_TRACE(i);
+            size_t offset = sampleIndex * pointerCount + i;
+            EXPECT_EQ(samplePointerCoords[offset].x,
+                    motionEvent->getHistoricalRawX(i, sampleIndex));
+            EXPECT_EQ(samplePointerCoords[offset].y,
+                    motionEvent->getHistoricalRawY(i, sampleIndex));
+            EXPECT_EQ(samplePointerCoords[offset].x + xOffset,
+                    motionEvent->getHistoricalX(i, sampleIndex));
+            EXPECT_EQ(samplePointerCoords[offset].y + yOffset,
+                    motionEvent->getHistoricalY(i, sampleIndex));
+            EXPECT_EQ(samplePointerCoords[offset].pressure,
+                    motionEvent->getHistoricalPressure(i, sampleIndex));
+            EXPECT_EQ(samplePointerCoords[offset].size,
+                    motionEvent->getHistoricalSize(i, sampleIndex));
+        }
+    }
+
+    SCOPED_TRACE(lastSampleIndex);
+    EXPECT_EQ(sampleEventTimes[lastSampleIndex], motionEvent->getEventTime());
+    for (size_t i = 0; i < pointerCount; i++) {
+        SCOPED_TRACE(i);
+        size_t offset = lastSampleIndex * pointerCount + i;
+        EXPECT_EQ(samplePointerCoords[offset].x, motionEvent->getRawX(i));
+        EXPECT_EQ(samplePointerCoords[offset].y, motionEvent->getRawY(i));
+        EXPECT_EQ(samplePointerCoords[offset].x + xOffset, motionEvent->getX(i));
+        EXPECT_EQ(samplePointerCoords[offset].y + yOffset, motionEvent->getY(i));
+        EXPECT_EQ(samplePointerCoords[offset].pressure, motionEvent->getPressure(i));
+        EXPECT_EQ(samplePointerCoords[offset].size, motionEvent->getSize(i));
+    }
+
+    status = mConsumer->sendFinishedSignal();
+    ASSERT_EQ(OK, status)
+            << "consumer sendFinishedSignal should return OK";
+
+    status = mPublisher->receiveFinishedSignal();
+    ASSERT_EQ(OK, status)
+            << "publisher receiveFinishedSignal should return OK";
+
+    status = mPublisher->reset();
+    ASSERT_EQ(OK, status)
+            << "publisher reset should return OK";
+}
+
+TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+}
+
+TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_WhenNotReset_ReturnsError) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+
+    status = mPublisher->publishKeyEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+    ASSERT_EQ(OK, status)
+            << "publisher publishKeyEvent should return OK first time";
+
+    status = mPublisher->publishKeyEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+    ASSERT_EQ(INVALID_OPERATION, status)
+            << "publisher publishKeyEvent should return INVALID_OPERATION because "
+                    "the publisher was not reset";
+}
+
+TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+}
+
+TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenNotReset_ReturnsError) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+
+    const size_t pointerCount = 1;
+    int32_t pointerIds[pointerCount] = { 0 };
+    PointerCoords pointerCoords[pointerCount] = { { 0, 0, 0, 0 } };
+
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            pointerCount, pointerIds, pointerCoords);
+    ASSERT_EQ(OK, status)
+            << "publisher publishMotionEvent should return OK";
+
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            pointerCount, pointerIds, pointerCoords);
+    ASSERT_EQ(INVALID_OPERATION, status)
+            << "publisher publishMotionEvent should return INVALID_OPERATION because ";
+                    "the publisher was not reset";
+}
+
+TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+
+    const size_t pointerCount = 0;
+    int32_t pointerIds[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            pointerCount, pointerIds, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status)
+            << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+
+    const size_t pointerCount = MAX_POINTERS + 1;
+    int32_t pointerIds[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            pointerCount, pointerIds, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status)
+            << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+}
+
+TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenCalledBeforeDispatchSignal_AppendsSamples) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent(3, 0));
+}
+
+TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenCalledAfterDispatchSignalAndNotConsumed_AppendsSamples) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent(0, 4));
+}
+
+TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenNoMotionEventPublished_ReturnsError) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+
+    PointerCoords pointerCoords[1];
+    status = mPublisher->appendMotionSample(0, pointerCoords);
+    ASSERT_EQ(INVALID_OPERATION, status)
+            << "publisher appendMotionSample should return INVALID_OPERATION";
+}
+
+TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenPublishedMotionEventIsNotAMove_ReturnsError) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+
+    const size_t pointerCount = MAX_POINTERS;
+    int32_t pointerIds[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_DOWN,
+            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+    ASSERT_EQ(OK, status);
+
+    status = mPublisher->appendMotionSample(0, pointerCoords);
+    ASSERT_EQ(INVALID_OPERATION, status)
+            << "publisher appendMotionSample should return INVALID_OPERATION";
+}
+
+TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenAlreadyConsumed_ReturnsError) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+
+    const size_t pointerCount = MAX_POINTERS;
+    int32_t pointerIds[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_MOVE,
+            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+    ASSERT_EQ(OK, status);
+
+    status = mPublisher->sendDispatchSignal();
+    ASSERT_EQ(OK, status);
+
+    status = mConsumer->receiveDispatchSignal();
+    ASSERT_EQ(OK, status);
+
+    InputEvent* event;
+    status = mConsumer->consume(& mEventFactory, & event);
+    ASSERT_EQ(OK, status);
+
+    status = mPublisher->appendMotionSample(0, pointerCoords);
+    ASSERT_EQ(status_t(FAILED_TRANSACTION), status)
+            << "publisher appendMotionSample should return FAILED_TRANSACTION";
+}
+
+TEST_F(InputPublisherAndConsumerTest, AppendMotionSample_WhenBufferFull_ReturnsError) {
+    status_t status;
+    ASSERT_NO_FATAL_FAILURE(Initialize());
+
+    const size_t pointerCount = MAX_POINTERS;
+    int32_t pointerIds[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    status = mPublisher->publishMotionEvent(0, 0, MOTION_EVENT_ACTION_MOVE,
+            0, 0, 0, 0, 0, 0, 0, 0, pointerCount, pointerIds, pointerCoords);
+    ASSERT_EQ(OK, status);
+
+    for (int count = 1;; count++) {
+        ASSERT_LT(count, 100000) << "should eventually reach OOM";
+
+        status = mPublisher->appendMotionSample(0, pointerCoords);
+        if (status != OK) {
+            ASSERT_GT(count, 12) << "should be able to add at least a dozen samples";
+            ASSERT_EQ(NO_MEMORY, status)
+                    << "publisher appendMotionSample should return NO_MEMORY when buffer is full";
+            break;
+        }
+    }
+
+    status = mPublisher->appendMotionSample(0, pointerCoords);
+    ASSERT_EQ(NO_MEMORY, status)
+            << "publisher appendMotionSample should return NO_MEMORY persistently until reset";
+}
+
+} // namespace android
diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp
index 90a3e8b..20a4d13 100644
--- a/libs/utils/PollLoop.cpp
+++ b/libs/utils/PollLoop.cpp
@@ -11,7 +11,7 @@
 #define DEBUG_POLL_AND_WAKE 0
 
 // Debugs callback registration and invocation.
-#define DEBUG_CALLBACKS 1
+#define DEBUG_CALLBACKS 0
 
 #include <cutils/log.h>
 #include <utils/PollLoop.h>
@@ -22,7 +22,7 @@
 namespace android {
 
 PollLoop::PollLoop() :
-        mPolling(false) {
+        mPolling(false), mWaiters(0) {
     openWakePipe();
 }
 
@@ -68,6 +68,9 @@
 
 bool PollLoop::pollOnce(int timeoutMillis) {
     mLock.lock();
+    while (mWaiters != 0) {
+        mResume.wait(mLock);
+    }
     mPolling = true;
     mLock.unlock();
 
@@ -156,7 +159,9 @@
 Done:
     mLock.lock();
     mPolling = false;
-    mAwake.broadcast();
+    if (mWaiters != 0) {
+        mAwake.broadcast();
+    }
     mLock.unlock();
 
     if (result) {
@@ -258,10 +263,15 @@
 
 void PollLoop::wakeAndLock() {
     mLock.lock();
+    mWaiters += 1;
     while (mPolling) {
         wake();
         mAwake.wait(mLock);
     }
+    mWaiters -= 1;
+    if (mWaiters == 0) {
+        mResume.signal();
+    }
 }
 
 } // namespace android
diff --git a/libs/utils/VectorImpl.cpp b/libs/utils/VectorImpl.cpp
index b09c6ca..289c826 100644
--- a/libs/utils/VectorImpl.cpp
+++ b/libs/utils/VectorImpl.cpp
@@ -108,7 +108,7 @@
 
 ssize_t VectorImpl::insertVectorAt(const VectorImpl& vector, size_t index)
 {
-    return insertAt(vector.arrayImpl(), index, vector.size());
+    return insertArrayAt(vector.arrayImpl(), index, vector.size());
 }
 
 ssize_t VectorImpl::appendVector(const VectorImpl& vector)
@@ -116,6 +116,22 @@
     return insertVectorAt(vector, size());
 }
 
+ssize_t VectorImpl::insertArrayAt(const void* array, size_t index, size_t length)
+{
+    if (index > size())
+        return BAD_INDEX;
+    void* where = _grow(index, length);
+    if (where) {
+        _do_copy(where, array, length);
+    }
+    return where ? index : (ssize_t)NO_MEMORY;
+}
+
+ssize_t VectorImpl::appendArray(const void* array, size_t length)
+{
+    return insertArrayAt(array, size(), length);
+}
+
 ssize_t VectorImpl::insertAt(size_t index, size_t numItems)
 {
     return insertAt(0, index, numItems);
@@ -220,9 +236,9 @@
     return add(0);
 }
 
-ssize_t VectorImpl::add(const void* item, size_t numItems)
+ssize_t VectorImpl::add(const void* item)
 {
-    return insertAt(item, size(), numItems);
+    return insertAt(item, size());
 }
 
 ssize_t VectorImpl::replaceAt(size_t index)
diff --git a/libs/utils/tests/PollLoop_test.cpp b/libs/utils/tests/PollLoop_test.cpp
index 6c719c8..4848c0f 100644
--- a/libs/utils/tests/PollLoop_test.cpp
+++ b/libs/utils/tests/PollLoop_test.cpp
@@ -16,34 +16,6 @@
 
 namespace android {
 
-class Pipe {
-public:
-    int sendFd;
-    int receiveFd;
-
-    Pipe() {
-        int fds[2];
-        ::pipe(fds);
-
-        receiveFd = fds[0];
-        sendFd = fds[1];
-    }
-
-    ~Pipe() {
-        ::close(sendFd);
-        ::close(receiveFd);
-    }
-
-    bool writeSignal() {
-        return ::write(sendFd, "*", 1) == 1;
-    }
-
-    bool readSignal() {
-        char buf[1];
-        return ::read(receiveFd, buf, 1) == 1;
-    }
-};
-
 class DelayedWake : public DelayedTask {
     sp<PollLoop> mPollLoop;
 
@@ -195,7 +167,7 @@
     Pipe pipe;
     StubCallbackHandler handler(true);
 
-    ASSERT_TRUE(pipe.writeSignal());
+    ASSERT_EQ(OK, pipe.writeSignal());
     handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
 
     StopWatch stopWatch("pollOnce");
@@ -243,7 +215,7 @@
     bool result = mPollLoop->pollOnce(100);
     int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
 
-    ASSERT_TRUE(pipe.readSignal())
+    ASSERT_EQ(OK, pipe.readSignal())
             << "signal should actually have been written";
     EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
             << "elapsed time should be approx. zero";
@@ -269,7 +241,7 @@
     bool result = mPollLoop->pollOnce(1000);
     int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
 
-    ASSERT_TRUE(pipe.readSignal())
+    ASSERT_EQ(OK, pipe.readSignal())
             << "signal should actually have been written";
     EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
             << "elapsed time should approx. equal signal delay";
@@ -295,7 +267,7 @@
     bool result = mPollLoop->pollOnce(100);
     int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
 
-    ASSERT_TRUE(pipe.readSignal())
+    ASSERT_EQ(OK, pipe.readSignal())
             << "signal should actually have been written";
     EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
             << "elapsed time should approx. equal timeout because FD was no longer registered";
@@ -318,7 +290,7 @@
     bool result = mPollLoop->pollOnce(0);
     int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
 
-    ASSERT_TRUE(pipe.readSignal())
+    ASSERT_EQ(OK, pipe.readSignal())
             << "signal should actually have been written";
     EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
             << "elapsed time should approx. equal zero because FD was already signalled";
@@ -334,7 +306,7 @@
     result = mPollLoop->pollOnce(0);
     elapsedMillis = ns2ms(stopWatch.elapsedTime());
 
-    ASSERT_TRUE(pipe.readSignal())
+    ASSERT_EQ(OK, pipe.readSignal())
             << "signal should actually have been written";
     EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
             << "elapsed time should approx. equal zero because timeout was zero";
@@ -382,7 +354,7 @@
     bool result = mPollLoop->pollOnce(100);
     int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
 
-    ASSERT_TRUE(pipe.readSignal())
+    ASSERT_EQ(OK, pipe.readSignal())
             << "signal should actually have been written";
     EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
             << "elapsed time should approx. zero because FD was already signalled";
diff --git a/libs/utils/tests/TestHelpers.h b/libs/utils/tests/TestHelpers.h
index e55af3c..d8e985e 100644
--- a/libs/utils/tests/TestHelpers.h
+++ b/libs/utils/tests/TestHelpers.h
@@ -21,6 +21,41 @@
 
 namespace android {
 
+class Pipe {
+public:
+    int sendFd;
+    int receiveFd;
+
+    Pipe() {
+        int fds[2];
+        ::pipe(fds);
+
+        receiveFd = fds[0];
+        sendFd = fds[1];
+    }
+
+    ~Pipe() {
+        if (sendFd != -1) {
+            ::close(sendFd);
+        }
+
+        if (receiveFd != -1) {
+            ::close(receiveFd);
+        }
+    }
+
+    status_t writeSignal() {
+        ssize_t nWritten = ::write(sendFd, "*", 1);
+        return nWritten == 1 ? 0 : -errno;
+    }
+
+    status_t readSignal() {
+        char buf[1];
+        ssize_t nRead = ::read(receiveFd, buf, 1);
+        return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno;
+    }
+};
+
 class DelayedTask : public Thread {
     int mDelayMillis;
 
diff --git a/native/include/android/input.h b/native/include/android/input.h
index ee2f664..193cbf3 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -394,6 +394,18 @@
  * in the java.lang.System.nanoTime() time base. */
 int64_t motion_event_get_event_time(const input_event_t* motion_event);
 
+/* Get the X coordinate offset.
+ * For touch events on the screen, this is the delta that was added to the raw
+ * screen coordinates to adjust for the absolute position of the containing windows
+ * and views. */
+float motion_event_get_x_offset(const input_event_t* motion_event);
+
+/* Get the precision of the Y coordinates being reported.
+ * For touch events on the screen, this is the delta that was added to the raw
+ * screen coordinates to adjust for the absolute position of the containing windows
+ * and views. */
+float motion_event_get_y_offset(const input_event_t* motion_event);
+
 /* Get the precision of the X coordinates being reported.
  * You can multiply this number with an X coordinate sample to find the
  * actual hardware value of the X coordinate. */
@@ -414,17 +426,17 @@
  * going up and down since the start of the current gesture. */
 int32_t motion_event_get_pointer_id(const input_event_t* motion_event, size_t pointer_index);
 
-/* Get the original raw X coordinate of this event.  For touch
- * events on the screen, this is the original location of the event
+/* Get the original raw X coordinate of this event.
+ * For touch events on the screen, this is the original location of the event
  * on the screen, before it had been adjusted for the containing window
  * and views. */
-float motion_event_get_raw_x(const input_event_t* motion_event);
+float motion_event_get_raw_x(const input_event_t* motion_event, size_t pointer_index);
 
-/* Get the original raw X coordinate of this event.  For touch
- * events on the screen, this is the original location of the event
+/* Get the original raw X coordinate of this event.
+ * For touch events on the screen, this is the original location of the event
  * on the screen, before it had been adjusted for the containing window
  * and views. */
-float motion_event_get_raw_y(const input_event_t* motion_event);
+float motion_event_get_raw_y(const input_event_t* motion_event, size_t pointer_index);
 
 /* Get the current X coordinate of this event for the given pointer index.
  * Whole numbers are pixels; the value may have a fraction for input devices
@@ -461,6 +473,24 @@
 int64_t motion_event_get_historical_event_time(input_event_t* motion_event,
         size_t history_index);
 
+/* Get the historical raw X coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float motion_event_get_historical_raw_x(const input_event_t* motion_event, size_t pointer_index);
+
+/* Get the historical raw Y coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float motion_event_get_historical_raw_y(const input_event_t* motion_event, size_t pointer_index);
+
 /* Get the historical X coordinate of this event for the given pointer index that
  * occurred between this event and the previous motion event.
  * Whole numbers are pixels; the value may have a fraction for input devices
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index a988a96..ab3922f 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -294,9 +294,7 @@
 
     if (wmActions & WM_ACTION_PASS_TO_USER) {
         actions |= InputReaderPolicyInterface::ACTION_DISPATCH;
-    }
 
-    if (! (wmActions & WM_ACTION_PASS_TO_USER)) {
         if (down && isAppSwitchKey(keyCode)) {
             env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyAppSwitchComing);
             checkExceptionFromCallback(env, "notifyAppSwitchComing");
@@ -312,12 +310,18 @@
     LOGD("interceptTouch - when=%lld", when);
 #endif
 
-    if (! isScreenOn()) {
-        // Touch events do not wake the device.
-        return InputReaderPolicyInterface::ACTION_NONE;
+    int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
+    if (isScreenOn()) {
+        // Only dispatch touch events when the device is awake.
+        actions |= InputReaderPolicyInterface::ACTION_DISPATCH;
     }
 
-    return InputReaderPolicyInterface::ACTION_DISPATCH;
+    if (! isScreenBright()) {
+        // Brighten the screen if dimmed.
+        actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE;
+    }
+
+    return actions;
 }
 
 int32_t NativeInputManager::interceptTrackball(nsecs_t when,
@@ -327,12 +331,18 @@
             when, buttonChanged, buttonDown, rolled);
 #endif
 
-    if (! isScreenOn()) {
-        // Trackball motions and button presses do not wake the device.
-        return InputReaderPolicyInterface::ACTION_NONE;
+    int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
+    if (isScreenOn()) {
+        // Only dispatch trackball events when the device is awake.
+        actions |= InputReaderPolicyInterface::ACTION_DISPATCH;
     }
 
-    return InputReaderPolicyInterface::ACTION_DISPATCH;
+    if (! isScreenBright()) {
+        // Brighten the screen if dimmed.
+        actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE;
+    }
+
+    return actions;
 }
 
 int32_t NativeInputManager::interceptSwitch(nsecs_t when, int32_t switchCode,