Finish implementation of multiple pointer support for MotionEvent.

The major things going on here:

- The MotionEvent API is now extended to included "pointer ID" information, for
  applications to keep track of individual fingers as they move up and down.
  PointerLocation has been updated to take advantage of this.

- The input system now has logic to generate MotionEvents with the new ID
  information, synthesizing an identifier as new points are down and trying to
  keep pointer ids consistent across events by looking at the distance between
  the last and next set of pointers.

- We now support the new multitouch driver protocol, and will use that instead
  of the old one if it is available.  We do NOT use any finger id information
  coming from the driver, but always synthesize pointer ids in user space.
  (This is simply because we don't yet have a driver reporting this information
  from which to base an implementation on.)

- Increase maximum number of fingers to 10.  This code has only been used
  with a driver that reports up to 2, so no idea how more will actually work.

- Oh and the input system can now detect and report physical DPAD devices.
diff --git a/api/current.xml b/api/current.xml
index f65b5ce..c4cac83 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -145940,6 +145940,19 @@
  visibility="public"
 >
 </method>
+<method name="findPointerIndex"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pointerId" type="int">
+</parameter>
+</method>
 <method name="getAction"
  return="int"
  abstract="false"
@@ -146031,7 +146044,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 <parameter name="pos" type="int">
 </parameter>
@@ -146059,7 +146072,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 <parameter name="pos" type="int">
 </parameter>
@@ -146087,7 +146100,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 <parameter name="pos" type="int">
 </parameter>
@@ -146115,7 +146128,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 <parameter name="pos" type="int">
 </parameter>
@@ -146153,6 +146166,19 @@
  visibility="public"
 >
 </method>
+<method name="getPointerId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="index" type="int">
+</parameter>
+</method>
 <method name="getPressure"
  return="float"
  abstract="false"
@@ -146174,7 +146200,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 </method>
 <method name="getRawX"
@@ -146220,7 +146246,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 </method>
 <method name="getX"
@@ -146244,7 +146270,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 </method>
 <method name="getXPrecision"
@@ -146279,7 +146305,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 </method>
 <method name="getYPrecision"
@@ -146615,7 +146641,7 @@
  visibility="public"
 >
 </field>
-<field name="ACTION_POINTER_MASK"
+<field name="ACTION_POINTER_ID_MASK"
  type="int"
  transient="false"
  volatile="false"
@@ -146626,7 +146652,7 @@
  visibility="public"
 >
 </field>
-<field name="ACTION_POINTER_SHIFT"
+<field name="ACTION_POINTER_ID_SHIFT"
  type="int"
  transient="false"
  volatile="false"
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index ae84e1e..d41d2d1 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.util.Log;
 
 /**
  * Object used to report movement (mouse, pen, finger, trackball) events.  This
@@ -26,6 +27,8 @@
  * it is being used for.
  */
 public final class MotionEvent implements Parcelable {
+    static final boolean DEBUG_POINTERS = false;
+    
     /**
      * Bit mask of the parts of the action code that are the action itself.
      */
@@ -68,58 +71,67 @@
 
     /**
      * A non-primary pointer has gone down.  The bits in
-     * {@link #ACTION_POINTER_MASK} indicate which pointer changed.
+     * {@link #ACTION_POINTER_ID_MASK} indicate which pointer changed.
      */
     public static final int ACTION_POINTER_DOWN     = 5;
     
     /**
-     * The primary pointer has gone done.
+     * Synonym for {@link #ACTION_POINTER_DOWN} with
+     * {@link #ACTION_POINTER_ID_MASK} of 0: the primary pointer has gone done.
      */
     public static final int ACTION_POINTER_1_DOWN   = ACTION_POINTER_DOWN | 0x0000;
     
     /**
-     * The secondary pointer has gone done.
+     * Synonym for {@link #ACTION_POINTER_DOWN} with
+     * {@link #ACTION_POINTER_ID_MASK} of 1: the secondary pointer has gone done.
      */
     public static final int ACTION_POINTER_2_DOWN   = ACTION_POINTER_DOWN | 0x0100;
     
     /**
-     * The tertiary pointer has gone done.
+     * Synonym for {@link #ACTION_POINTER_DOWN} with
+     * {@link #ACTION_POINTER_ID_MASK} of 2: the tertiary pointer has gone done.
      */
     public static final int ACTION_POINTER_3_DOWN   = ACTION_POINTER_DOWN | 0x0200;
     
     /**
      * A non-primary pointer has gone up.  The bits in
-     * {@link #ACTION_POINTER_MASK} indicate which pointer changed.
+     * {@link #ACTION_POINTER_ID_MASK} indicate which pointer changed.
      */
     public static final int ACTION_POINTER_UP       = 6;
     
     /**
-     * The primary pointer has gone up.
+     * Synonym for {@link #ACTION_POINTER_UP} with
+     * {@link #ACTION_POINTER_ID_MASK} of 0: the primary pointer has gone up.
      */
     public static final int ACTION_POINTER_1_UP     = ACTION_POINTER_UP | 0x0000;
     
     /**
-     * The secondary pointer has gone up.
+     * Synonym for {@link #ACTION_POINTER_UP} with
+     * {@link #ACTION_POINTER_ID_MASK} of 1: the secondary pointer has gone up.
      */
     public static final int ACTION_POINTER_2_UP     = ACTION_POINTER_UP | 0x0100;
     
     /**
-     * The tertiary pointer has gone up.
+     * Synonym for {@link #ACTION_POINTER_UP} with
+     * {@link #ACTION_POINTER_ID_MASK} of 2: the tertiary pointer has gone up.
      */
     public static final int ACTION_POINTER_3_UP     = ACTION_POINTER_UP | 0x0200;
     
     /**
      * Bits in the action code that represent a pointer ID, used with
      * {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}.  Pointer IDs
-     * start at 0, with 0 being the primary (first) pointer in the motion.
+     * start at 0, with 0 being the primary (first) pointer in the motion.  Note
+     * that this not <em>not</em> an index into the array of pointer values,
+     * which is compacted to only contain pointers that are down; the pointer
+     * ID for a particular index can be found with {@link #findPointerIndex}.
      */
-    public static final int ACTION_POINTER_MASK     = 0xff00;
+    public static final int ACTION_POINTER_ID_MASK  = 0xff00;
     
     /**
      * Bit shift for the action bits holding the pointer identifier as
-     * defined by {@link #ACTION_POINTER_MASK}.
+     * defined by {@link #ACTION_POINTER_ID_MASK}.
      */
-    public static final int ACTION_POINTER_SHIFT    = 8;
+    public static final int ACTION_POINTER_ID_SHIFT = 8;
     
     private static final boolean TRACK_RECYCLED_LOCATION = false;
 
@@ -144,18 +156,6 @@
     public static final int EDGE_RIGHT = 0x00000008;
 
     /**
-     * This is the part of the state data that holds the finger identifier
-     * for the sample.
-     */
-    static private final int STATE_FINGER_ID_MASK = 0xff;
-    
-    /**
-     * Special value for STATE_FINGER_ID_MASK indicating that the finger
-     * is not down in that sample.
-     */
-    static private final int STATE_FINGER_ID_NONE = 0xff;
-    
-    /**
      * Offset for the sample's X coordinate.
      * @hide
      */
@@ -212,8 +212,8 @@
     
     private int mNumPointers;
     private int mNumSamples;
-    // Array of (mNumSamples * mNumPointers) size of control data.
-    private int[] mStateSamples;
+    // 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.
     private float[] mDataSamples;
     // Array of mNumSamples size of time stamps.
@@ -224,7 +224,7 @@
     private boolean mRecycled;
 
     private MotionEvent() {
-        mStateSamples = new int[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES];
+        mPointerIdentifiers = new int[BASE_AVAIL_POINTERS];
         mDataSamples = new float[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES*NUM_SAMPLE_DATA];
         mTimeSamples = new long[BASE_AVAIL_SAMPLES];
     }
@@ -256,16 +256,11 @@
      * @param action The kind of action being performed -- one of either
      * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
      * {@link #ACTION_CANCEL}.
-     * @param x The X coordinate of this event.
-     * @param y The Y coordinate of this event.
-     * @param pressure The current pressure of this event.  The pressure generally 
-     * ranges from 0 (no pressure at all) to 1 (normal pressure), however 
-     * values higher than 1 may be generated depending on the calibration of 
-     * the input device.
-     * @param size A scaled value of the approximate size of the area being pressed when
-     * touched with the finger. The actual value in pixels corresponding to the finger 
-     * touch is normalized with a device specific range of values
-     * and scaled to a value between 0 and 1.
+     * @param pointers The number of points that will be in this event.
+     * @param inPointerIds An array of <em>pointers</em> values providing
+     * an identifier for each pointer.
+     * @param inData An array of <em>pointers*NUM_SAMPLE_DATA</em> of initial
+     * data samples for the event.
      * @param metaState The state of any meta / modifier keys that were in effect when
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
@@ -279,7 +274,7 @@
      * @hide
      */
     static public MotionEvent obtainNano(long downTime, long eventTime, long eventTimeNano,
-            int action, int pointers, float[] inData, int metaState,
+            int action, int pointers, int[] inPointerIds, float[] inData, int metaState,
             float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
         MotionEvent ev = obtain();
         ev.mDeviceId = deviceId;
@@ -295,17 +290,25 @@
         ev.mNumPointers = pointers;
         ev.mNumSamples = 1;
         
-        float[] data = ev.mDataSamples;
-        System.arraycopy(inData, 0, data, 0, pointers * NUM_SAMPLE_DATA);
-        
-        int[] state = ev.mStateSamples;
-        while (pointers > 0) {
-            pointers--;
-            state[pointers] = pointers;
-        }
-        
+        System.arraycopy(inPointerIds, 0, ev.mPointerIdentifiers, 0, pointers);
+        System.arraycopy(inData, 0, ev.mDataSamples, 0, pointers * NUM_SAMPLE_DATA);
         ev.mTimeSamples[0] = eventTime;
 
+        if (DEBUG_POINTERS) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("New:");
+            for (int i=0; i<pointers; i++) {
+                sb.append(" #");
+                sb.append(ev.mPointerIdentifiers[i]);
+                sb.append("(");
+                sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]);
+                sb.append(",");
+                sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]);
+                sb.append(")");
+            }
+            Log.v("MotionEvent", sb.toString());
+        }
+        
         return ev;
     }
     
@@ -355,8 +358,8 @@
 
         ev.mNumPointers = 1;
         ev.mNumSamples = 1;
-        int[] state = ev.mStateSamples;
-        state[0] = 0;
+        int[] pointerIds = ev.mPointerIdentifiers;
+        pointerIds[0] = 0;
         float[] data = ev.mDataSamples;
         data[SAMPLE_X] = ev.mRawX = x;
         data[SAMPLE_Y] = ev.mRawY = y;
@@ -415,8 +418,8 @@
 
         ev.mNumPointers = 1;
         ev.mNumSamples = 1;
-        int[] state = ev.mStateSamples;
-        state[0] = 0;
+        int[] pointerIds = ev.mPointerIdentifiers;
+        pointerIds[0] = 0;
         float[] data = ev.mDataSamples;
         data[SAMPLE_X] = ev.mRawX = x;
         data[SAMPLE_Y] = ev.mRawY = y;
@@ -459,8 +462,8 @@
 
         ev.mNumPointers = 1;
         ev.mNumSamples = 1;
-        int[] state = ev.mStateSamples;
-        state[0] = 0;
+        int[] pointerIds = ev.mPointerIdentifiers;
+        pointerIds[0] = 0;
         float[] data = ev.mDataSamples;
         data[SAMPLE_X] = ev.mRawX = x;
         data[SAMPLE_Y] = ev.mRawY = y;
@@ -508,21 +511,21 @@
         ev.mXPrecision = o.mXPrecision;
         ev.mYPrecision = o.mYPrecision;
         
-        final int NT = ev.mNumSamples = o.mNumSamples;
-        if (ev.mTimeSamples.length >= NT) {
-            System.arraycopy(o.mTimeSamples, 0, ev.mTimeSamples, 0, NT);
+        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();
         }
         
-        final int NS = (ev.mNumPointers=o.mNumPointers) * NT;
-        if (ev.mStateSamples.length >= NS) {
-            System.arraycopy(o.mStateSamples, 0, ev.mStateSamples, 0, NS);
+        final int NP = (ev.mNumPointers=o.mNumPointers);
+        if (ev.mPointerIdentifiers.length >= NP) {
+            System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, NP);
         } else {
-            ev.mStateSamples = (int[])o.mStateSamples.clone();
+            ev.mPointerIdentifiers = (int[])o.mPointerIdentifiers.clone();
         }
         
-        final int ND = NS * NUM_SAMPLE_DATA;
+        final int ND = NP * NS * NUM_SAMPLE_DATA;
         if (ev.mDataSamples.length >= ND) {
             System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, ND);
         } else {
@@ -593,6 +596,38 @@
     }
 
     /**
+     * {@link #getX(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     */
+    public final float getX() {
+        return mDataSamples[SAMPLE_X];
+    }
+
+    /**
+     * {@link #getY(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     */
+    public final float getY() {
+        return mDataSamples[SAMPLE_Y];
+    }
+
+    /**
+     * {@link #getPressure(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     */
+    public final float getPressure() {
+        return mDataSamples[SAMPLE_PRESSURE];
+    }
+
+    /**
+     * {@link #getSize(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     */
+    public final float getSize() {
+        return mDataSamples[SAMPLE_SIZE];
+    }
+
+    /**
      * The number of pointers of data contained in this event.  Always
      * >= 1.
      */
@@ -601,80 +636,91 @@
     }
     
     /**
-     * {@link #getX(int)} for the first pointer (pointer 0).
+     * Return the pointer identifier associated with a particular pointer
+     * data index is this event.  The identifier tells you the actual pointer
+     * number associated with the data, accounting for individual pointers
+     * going up and down since the start of the current gesture.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getX() {
-        return mDataSamples[SAMPLE_X];
+    public final int getPointerId(int index) {
+        return mPointerIdentifiers[index];
     }
-
+    
     /**
-     * {@link #getY(int)} for the first pointer (pointer 0).
+     * Given a pointer identifier, find the index of its data in the event.
+     * 
+     * @param pointerId The identifier of the pointer to be found.
+     * @return Returns either the index of the pointer (for use with
+     * {@link #getX(int) et al.), or -1 if there is no data available for
+     * that pointer identifier.
      */
-    public final float getY() {
-        return mDataSamples[SAMPLE_Y];
+    public final int findPointerIndex(int pointerId) {
+        int i = mNumPointers;
+        while (i > 0) {
+            i--;
+            if (mPointerIdentifiers[i] == pointerId) {
+                return i;
+            }
+        }
+        return -1;
     }
-
+    
     /**
-     * {@link #getPressure(int)} for the first pointer (pointer 0).
-     */
-    public final float getPressure() {
-        return mDataSamples[SAMPLE_PRESSURE];
-    }
-
-    /**
-     * {@link #getSize(int)} for the first pointer (pointer 0).
-     */
-    public final float getSize() {
-        return mDataSamples[SAMPLE_SIZE];
-    }
-
-    /**
-     * Returns the X coordinate of this event for the given pointer.
+     * Returns the X coordinate of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
      * Whole numbers are pixels; the 
      * value may have a fraction for input devices that are sub-pixel precise. 
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getX(int pointer) {
-        return mDataSamples[(pointer*NUM_SAMPLE_DATA) + SAMPLE_X];
+    public final float getX(int pointerIndex) {
+        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_X];
     }
 
     /**
-     * Returns the Y coordinate of this event for the given pointer.
+     * Returns the Y coordinate of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
      * Whole numbers are pixels; the
      * value may have a fraction for input devices that are sub-pixel precise.
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getY(int pointer) {
-        return mDataSamples[(pointer*NUM_SAMPLE_DATA) + SAMPLE_Y];
+    public final float getY(int pointerIndex) {
+        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_Y];
     }
 
     /**
-     * Returns the current pressure of this event for the given pointer.
+     * Returns the current pressure of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
      * The pressure generally
      * ranges from 0 (no pressure at all) to 1 (normal pressure), however
      * values higher than 1 may be generated depending on the calibration of
      * the input device.
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getPressure(int pointer) {
-        return mDataSamples[(pointer*NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
+    public final float getPressure(int pointerIndex) {
+        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
     }
 
     /**
-     * Returns a scaled value of the approximate size for the given pointer,
-     * representing the area of the screen being pressed.
-     * The actual value in pixels corresponding to the
+     * Returns a scaled value of the approximate size for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * This represents some approximation of the area of the screen being
+     * pressed; the actual value in pixels corresponding to the
      * touch is normalized with the device specific range of values
      * and scaled to a value between 0 and 1. The value of size can be used to
      * determine fat touch events.
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getSize(int pointer) {
-        return mDataSamples[(pointer*NUM_SAMPLE_DATA) + SAMPLE_SIZE];
+    public final float getSize(int pointerIndex) {
+        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_SIZE];
     }
 
     /**
@@ -758,99 +804,107 @@
     }
 
     /**
-     * {@link #getHistoricalX(int)} for the first pointer (pointer 0).
+     * {@link #getHistoricalX(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
      */
     public final float getHistoricalX(int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_X];
     }
 
     /**
-     * {@link #getHistoricalY(int)} for the first pointer (pointer 0).
+     * {@link #getHistoricalY(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
      */
     public final float getHistoricalY(int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_Y];
     }
 
     /**
-     * {@link #getHistoricalPressure(int)} for the first pointer (pointer 0).
+     * {@link #getHistoricalPressure(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
      */
     public final float getHistoricalPressure(int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_PRESSURE];
     }
 
     /**
-     * {@link #getHistoricalSize(int)} for the first pointer (pointer 0).
+     * {@link #getHistoricalSize(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
      */
     public final float getHistoricalSize(int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_SIZE];
     }
 
     /**
-     * Returns a historical X coordinate that occurred between this event
-     * and the previous event for the given pointer.  Only applies to ACTION_MOVE events.
+     * Returns a historical X coordinate, as per {@link #getX(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
      *
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      * @param pos Which historical value to return; must be less than
      * {@link #getHistorySize}
      *
      * @see #getHistorySize
      * @see #getX
      */
-    public final float getHistoricalX(int pointer, int pos) {
+    public final float getHistoricalX(int pointerIndex, int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointer * NUM_SAMPLE_DATA) + SAMPLE_X];
+                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_X];
     }
 
     /**
-     * Returns a historical Y coordinate that occurred between this event
-     * and the previous event for the given pointer.  Only applies to ACTION_MOVE events.
+     * Returns a historical Y coordinate, as per {@link #getY(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
      *
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      * @param pos Which historical value to return; must be less than
      * {@link #getHistorySize}
      *
      * @see #getHistorySize
      * @see #getY
      */
-    public final float getHistoricalY(int pointer, int pos) {
+    public final float getHistoricalY(int pointerIndex, int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointer * NUM_SAMPLE_DATA) + SAMPLE_Y];
+                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_Y];
     }
 
     /**
-     * Returns a historical pressure coordinate that occurred between this event
-     * and the previous event for the given pointer.  Only applies to ACTION_MOVE events.
+     * Returns a historical pressure coordinate, as per {@link #getPressure(int)},
+     * that occurred between this event and the previous event for the given
+     * pointer.  Only applies to ACTION_MOVE events.
      *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      * @param pos Which historical value to return; must be less than
      * {@link #getHistorySize}
-     *
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * 
      * @see #getHistorySize
      * @see #getPressure
      */
-    public final float getHistoricalPressure(int pointer, int pos) {
+    public final float getHistoricalPressure(int pointerIndex, int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointer * NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
+                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
     }
 
     /**
-     * Returns a historical size coordinate that occurred between this event
-     * and the previous event for the given pointer.  Only applies to ACTION_MOVE events.
+     * Returns a historical size coordinate, as per {@link #getSize(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
      *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      * @param pos Which historical value to return; must be less than
      * {@link #getHistorySize}
-     *
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * 
      * @see #getHistorySize
      * @see #getSize
      */
-    public final float getHistoricalSize(int pointer, int pos) {
+    public final float getHistoricalSize(int pointerIndex, int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointer * NUM_SAMPLE_DATA) + SAMPLE_SIZE];
+                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_SIZE];
     }
 
     /**
@@ -938,7 +992,6 @@
      */
     public final void addBatch(long eventTime, float x, float y,
             float pressure, float size, int metaState) {
-        int[] states = mStateSamples;
         float[] data = mDataSamples;
         long[] times = mTimeSamples;
         
@@ -946,14 +999,8 @@
         final int NS = mNumSamples;
         final int NI = NP*NS;
         final int ND = NI * NUM_SAMPLE_DATA;
-        if (states.length < (NI+NP)) {
-            // The state and data arrays are sized together, since their
-            // size is always a fixed factor from each other.
-            final int NEW_NI = NP * (NS+BASE_AVAIL_SAMPLES);
-            int[] newState = new int[NEW_NI];
-            System.arraycopy(states, 0, newState, 0, NI);
-            mStateSamples = states = newState;
-            final int NEW_ND = NEW_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;
@@ -996,7 +1043,6 @@
      * @hide
      */
     public final void addBatch(long eventTime, float[] inData, int metaState) {
-        int[] states = mStateSamples;
         float[] data = mDataSamples;
         long[] times = mTimeSamples;
         
@@ -1004,14 +1050,8 @@
         final int NS = mNumSamples;
         final int NI = NP*NS;
         final int ND = NI * NUM_SAMPLE_DATA;
-        if (states.length < (NI+NP)) {
-            // The state and data arrays are sized together, since their
-            // size is always a fixed factor from each other.
-            final int NEW_NI = NP * (NS+BASE_AVAIL_SAMPLES);
-            int[] newState = new int[NEW_NI];
-            System.arraycopy(states, 0, newState, 0, NI);
-            mStateSamples = states = newState;
-            final int NEW_ND = NEW_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;
@@ -1034,6 +1074,21 @@
         mRawX = inData[SAMPLE_X];
         mRawY = inData[SAMPLE_Y];
         mMetaState |= metaState;
+        
+        if (DEBUG_POINTERS) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Add:");
+            for (int i=0; i<mNumPointers; i++) {
+                sb.append(" #");
+                sb.append(mPointerIdentifiers[i]);
+                sb.append("(");
+                sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]);
+                sb.append(",");
+                sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]);
+                sb.append(")");
+            }
+            Log.v("MotionEvent", sb.toString());
+        }
     }
 
     @Override
@@ -1074,13 +1129,13 @@
         final int NI = NP*NS;
         if (NI > 0) {
             int i;
-            int[] state = mStateSamples;
-            for (i=0; i<NI; i++) {
+            int[] state = mPointerIdentifiers;
+            for (i=0; i<NP; i++) {
                 out.writeInt(state[i]);
             }
-            final int NI4 = NI*NUM_SAMPLE_DATA;
+            final int ND = NI*NUM_SAMPLE_DATA;
             float[] history = mDataSamples;
-            for (i=0; i<NI4; i++) {
+            for (i=0; i<ND; i++) {
                 out.writeFloat(history[i]);
             }
             long[] times = mTimeSamples;
@@ -1107,19 +1162,19 @@
         mNumSamples = NS;
         final int NI = NP*NS;
         if (NI > 0) {
-            final int NI4 = NI*4;
-            int[] state = mStateSamples;
-            if (state.length < NI) {
-                mStateSamples = state = new int[NI];
+            int[] ids = mPointerIdentifiers;
+            if (ids.length < NP) {
+                mPointerIdentifiers = ids = new int[NP];
             }
-            for (int i=0; i<NI; i++) {
-                state[i] = in.readInt();
+            for (int i=0; i<NP; i++) {
+                ids[i] = in.readInt();
             }
             float[] history = mDataSamples;
-            if (history.length < NI4) {
-                mDataSamples = history = new float[NI4];
+            final int ND = NI*NUM_SAMPLE_DATA;
+            if (history.length < ND) {
+                mDataSamples = history = new float[ND];
             }
-            for (int i=0; i<NI4; i++) {
+            for (int i=0; i<ND; i++) {
                 history[i] = in.readFloat();
             }
             long[] times = mTimeSamples;
diff --git a/core/java/android/view/RawInputEvent.java b/core/java/android/view/RawInputEvent.java
index 30da83e..db024b4 100644
--- a/core/java/android/view/RawInputEvent.java
+++ b/core/java/android/view/RawInputEvent.java
@@ -13,6 +13,8 @@
     public static final int CLASS_ALPHAKEY = 0x00000002;
     public static final int CLASS_TOUCHSCREEN = 0x00000004;
     public static final int CLASS_TRACKBALL = 0x00000008;
+    public static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
+    public static final int CLASS_DPAD = 0x00000020;
     
     // More special classes for QueuedEvent below.
     public static final int CLASS_CONFIGURATION_CHANGED = 0x10000000;
@@ -158,8 +160,21 @@
     public static final int ABS_TOOL_WIDTH = 0x1c;
     public static final int ABS_VOLUME = 0x20;
     public static final int ABS_MISC = 0x28;
+    public static final int ABS_MT_TOUCH_MAJOR = 0x30;
+    public static final int ABS_MT_TOUCH_MINOR = 0x31;
+    public static final int ABS_MT_WIDTH_MAJOR = 0x32;
+    public static final int ABS_MT_WIDTH_MINOR = 0x33;
+    public static final int ABS_MT_ORIENTATION = 0x34;
+    public static final int ABS_MT_POSITION_X = 0x35;
+    public static final int ABS_MT_POSITION_Y = 0x36;
+    public static final int ABS_MT_TOOL_TYPE = 0x37;
+    public static final int ABS_MT_BLOB_ID = 0x38;
     public static final int ABS_MAX = 0x3f;
 
+    public static final int SYN_REPORT = 0;
+    public static final int SYN_CONFIG = 1;
+    public static final int SYN_MT_REPORT = 2;
+    
     public int deviceId;
     public int type;
     public int scancode;
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index d62fd7d..bffba07 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -55,7 +55,9 @@
         CLASS_KEYBOARD      = 0x00000001,
         CLASS_ALPHAKEY      = 0x00000002,
         CLASS_TOUCHSCREEN   = 0x00000004,
-        CLASS_TRACKBALL     = 0x00000008
+        CLASS_TRACKBALL     = 0x00000008,
+        CLASS_TOUCHSCREEN_MT= 0x00000010,
+        CLASS_DPAD          = 0x00000020
     };
     uint32_t getDeviceClasses(int32_t deviceId) const;
     
@@ -122,6 +124,7 @@
     };
 
     device_t* getDevice(int32_t deviceId) const;
+    bool hasKeycode(device_t* device, int keycode) const;
     
     // Protect all internal state.
     mutable Mutex   mLock;
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 59c9476..27334b7 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -16,6 +16,7 @@
 //#define LOG_NDEBUG 0
 
 #include <ui/EventHub.h>
+#include <ui/KeycodeLabels.h>
 #include <hardware_legacy/power.h>
 
 #include <cutils/properties.h>
@@ -58,6 +59,18 @@
 #define SEQ_SHIFT 16
 #define id_to_index(id)         ((id&ID_MASK)+1)
 
+#ifndef ABS_MT_TOUCH_MAJOR
+#define ABS_MT_TOUCH_MAJOR      0x30    /* Major axis of touching ellipse */
+#endif
+
+#ifndef ABS_MT_POSITION_X
+#define ABS_MT_POSITION_X       0x35    /* Center X ellipse position */
+#endif
+
+#ifndef ABS_MT_POSITION_Y
+#define ABS_MT_POSITION_Y       0x36    /* Center Y ellipse position */
+#endif
+
 namespace android {
 
 static const char *WAKE_LOCK_ID = "KeyEvents";
@@ -590,6 +603,8 @@
     mFDs[mFDCount].events = POLLIN;
 
     // figure out the kinds of events the device reports
+    
+    // See if this is a keyboard, and classify it.
     uint8_t key_bitmask[(KEY_MAX+1)/8];
     memset(key_bitmask, 0, sizeof(key_bitmask));
     LOGV("Getting keys...");
@@ -601,15 +616,11 @@
         for (int i=0; i<((BTN_MISC+7)/8); i++) {
             if (key_bitmask[i] != 0) {
                 device->classes |= CLASS_KEYBOARD;
-                // 'Q' key support = cheap test of whether this is an alpha-capable kbd
-                if (test_bit(KEY_Q, key_bitmask)) {
-                    device->classes |= CLASS_ALPHAKEY;
-                }
                 break;
             }
         }
         if ((device->classes & CLASS_KEYBOARD) != 0) {
-            device->keyBitmask = new uint8_t[(KEY_MAX+1)/8];
+            device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
             if (device->keyBitmask != NULL) {
                 memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
             } else {
@@ -619,6 +630,8 @@
             }
         }
     }
+    
+    // See if this is a trackball.
     if (test_bit(BTN_MOUSE, key_bitmask)) {
         uint8_t rel_bitmask[(REL_MAX+1)/8];
         memset(rel_bitmask, 0, sizeof(rel_bitmask));
@@ -630,16 +643,22 @@
             }
         }
     }
-    if (test_bit(BTN_TOUCH, key_bitmask)) {
-        uint8_t abs_bitmask[(ABS_MAX+1)/8];
-        memset(abs_bitmask, 0, sizeof(abs_bitmask));
-        LOGV("Getting absolute controllers...");
-        if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) >= 0)
-        {
-            if (test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
-                device->classes |= CLASS_TOUCHSCREEN;
-            }
-        }
+    
+    uint8_t abs_bitmask[(ABS_MAX+1)/8];
+    memset(abs_bitmask, 0, sizeof(abs_bitmask));
+    LOGV("Getting absolute controllers...");
+    ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask);
+    
+    // Is this a new modern multi-touch driver?
+    if (test_bit(ABS_MT_TOUCH_MAJOR, abs_bitmask)
+            && test_bit(ABS_MT_POSITION_X, abs_bitmask)
+            && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) {
+        device->classes |= CLASS_TOUCHSCREEN | CLASS_TOUCHSCREEN_MT;
+        
+    // Is this an old style single-touch driver?
+    } else if (test_bit(BTN_TOUCH, key_bitmask)
+            && test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
+        device->classes |= CLASS_TOUCHSCREEN;
     }
 
 #ifdef EV_SW
@@ -658,9 +677,6 @@
     }
 #endif
 
-    LOGI("New device: path=%s name=%s id=0x%x (of 0x%x) index=%d fd=%d classes=0x%x\n",
-         deviceName, name, device->id, mNumDevicesById, mFDCount, fd, device->classes);
-
     if ((device->classes&CLASS_KEYBOARD) != 0) {
         char devname[101];
         char tmpfn[101];
@@ -707,10 +723,27 @@
         sprintf(propName, "hw.keyboards.%u.devname", publicID);
         property_set(propName, devname);
 
-        LOGI("New keyboard: publicID=%d device->id=%d devname='%s' propName='%s' keylayout='%s'\n",
+        // 'Q' key support = cheap test of whether this is an alpha-capable kbd
+        if (hasKeycode(device, kKeyCodeQ)) {
+            device->classes |= CLASS_ALPHAKEY;
+        }
+        
+        // See if this has a DPAD.
+        if (hasKeycode(device, kKeyCodeDpadUp) &&
+                hasKeycode(device, kKeyCodeDpadDown) &&
+                hasKeycode(device, kKeyCodeDpadLeft) &&
+                hasKeycode(device, kKeyCodeDpadRight) &&
+                hasKeycode(device, kKeyCodeDpadCenter)) {
+            device->classes |= CLASS_DPAD;
+        }
+        
+        LOGI("New keyboard: publicID=%d device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n",
                 publicID, device->id, devname, propName, keylayoutFilename);
     }
 
+    LOGI("New device: path=%s name=%s id=0x%x (of 0x%x) index=%d fd=%d classes=0x%x\n",
+         deviceName, name, device->id, mNumDevicesById, mFDCount, fd, device->classes);
+         
     LOGV("Adding device %s %p at %d, id = %d, classes = 0x%x\n",
          deviceName, device, mFDCount, devid, device->classes);
 
@@ -723,6 +756,25 @@
     return 0;
 }
 
+bool EventHub::hasKeycode(device_t* device, int keycode) const
+{
+    if (device->keyBitmask == NULL || device->layoutMap == NULL) {
+        return false;
+    }
+    
+    Vector<int32_t> scanCodes;
+    device->layoutMap->findScancodes(keycode, &scanCodes);
+    const size_t N = scanCodes.size();
+    for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+        int32_t sc = scanCodes.itemAt(i);
+        if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) {
+            return true;
+        }
+    }
+    
+    return false;
+}
+
 int EventHub::close_device(const char *deviceName)
 {
     AutoMutex _l(mLock);
diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java
index 0ac5740..cb23c45 100644
--- a/services/java/com/android/server/InputDevice.java
+++ b/services/java/com/android/server/InputDevice.java
@@ -23,11 +23,13 @@
 import android.view.WindowManagerPolicy;
 
 public class InputDevice {
+    static final boolean DEBUG_POINTERS = false;
+    
     /** Amount that trackball needs to move in order to generate a key event. */
     static final int TRACKBALL_MOVEMENT_THRESHOLD = 6;
 
     /** Maximum number of pointers we will track and report. */
-    static final int MAX_POINTERS = 2;
+    static final int MAX_POINTERS = 10;
     
     final int id;
     final int classes;
@@ -51,100 +53,316 @@
         float yMoveScale;
         MotionEvent currentMove = null;
         boolean changed = false;
-        boolean mLastAnyDown = false;
         long mDownTime = 0;
-        final boolean[] mLastDown = new boolean[MAX_POINTERS];
-        final boolean[] mDown = new boolean[MAX_POINTERS];
+        
+        // The currently assigned pointer IDs, corresponding to the last data.
+        int[] mPointerIds = new int[MAX_POINTERS];
+        
+        // This is the last generated pointer data, ordered to match
+        // mPointerIds.
+        int mLastNumPointers = 0;
         final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];
-        final int[] mCurData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];
+        
+        // This is the next set of pointer data being generated.  It is not
+        // in any known order, and will be propagated in to mLastData
+        // as part of mapping it to the appropriate pointer IDs.
+        // Note that we have one extra sample of data here, to help clients
+        // avoid doing bounds checking.
+        int mNextNumPointers = 0;
+        final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS)
+                                        + MotionEvent.NUM_SAMPLE_DATA];
+        
+        // Temporary data structures for doing the pointer ID mapping.
+        final int[] mLast2Next = new int[MAX_POINTERS];
+        final int[] mNext2Last = new int[MAX_POINTERS];
+        final long[] mNext2LastDistance = new long[MAX_POINTERS];
+        
+        // Temporary data structure for generating the final motion data.
         final float[] mReportData = new float[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];
         
+        // This is not used here, but can be used by callers for state tracking.
+        int mAddingPointerOffset = 0;
+        final boolean[] mDown = new boolean[MAX_POINTERS];
+        
         MotionState(int mx, int my) {
             xPrecision = mx;
             yPrecision = my;
             xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f;
             yMoveScale = my != 0 ? (1.0f/my) : 1.0f;
+            for (int i=0; i<MAX_POINTERS; i++) {
+                mPointerIds[i] = i;
+            }
+        }
+        
+        private boolean assignPointer(int nextIndex, boolean allowOverlap) {
+            final int lastNumPointers = mLastNumPointers;
+            final int[] next2Last = mNext2Last;
+            final long[] next2LastDistance = mNext2LastDistance;
+            final int[] last2Next = mLast2Next;
+            final int[] lastData = mLastData;
+            final int[] nextData = mNextData;
+            final int id = nextIndex * MotionEvent.NUM_SAMPLE_DATA;
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice", "assignPointer: nextIndex="
+                    + nextIndex + " dataOff=" + id);
+            final int x1 = nextData[id + MotionEvent.SAMPLE_X];
+            final int y1 = nextData[id + MotionEvent.SAMPLE_Y];
+            
+            long bestDistance = -1;
+            int bestIndex = -1;
+            for (int j=0; j<lastNumPointers; j++) {
+                if (!allowOverlap && last2Next[j] < 0) {
+                    continue;
+                }
+                final int jd = j * MotionEvent.NUM_SAMPLE_DATA;
+                final int xd = lastData[jd + MotionEvent.SAMPLE_X] - x1;
+                final int yd = lastData[jd + MotionEvent.SAMPLE_Y] - y1;
+                final long distance = xd*(long)xd + yd*(long)yd;
+                if (j == 0 || distance < bestDistance) {
+                    bestDistance = distance;
+                    bestIndex = j;
+                }
+            }
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice", "New index " + nextIndex
+                    + " best old index=" + bestIndex + " (distance="
+                    + bestDistance + ")");
+            next2Last[nextIndex] = bestIndex;
+            next2LastDistance[nextIndex] = bestDistance;
+            
+            if (bestIndex < 0) {
+                return true;
+            }
+            
+            if (last2Next[bestIndex] == -1) {
+                last2Next[bestIndex] = nextIndex;
+                return false;
+            }
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice", "Old index " + bestIndex
+                    + " has multiple best new pointers!");
+            
+            last2Next[bestIndex] = -2;
+            return true;
+        }
+        
+        private int updatePointerIdentifiers() {
+            final int[] lastData = mLastData;
+            final int[] nextData = mNextData;
+            final int nextNumPointers = mNextNumPointers;
+            final int lastNumPointers = mLastNumPointers;
+            
+            if (nextNumPointers == 1 && lastNumPointers == 1) {
+                System.arraycopy(nextData, 0, lastData, 0,
+                        MotionEvent.NUM_SAMPLE_DATA);
+                return -1;
+            }
+            
+            // Clear our old state.
+            final int[] last2Next = mLast2Next;
+            for (int i=0; i<lastNumPointers; i++) {
+                last2Next[i] = -1;
+            }
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice",
+                    "Update pointers: lastNumPointers=" + lastNumPointers
+                    + " nextNumPointers=" + nextNumPointers);
+            
+            // Figure out the closes new points to the previous points.
+            final int[] next2Last = mNext2Last;
+            final long[] next2LastDistance = mNext2LastDistance;
+            boolean conflicts = false;
+            for (int i=0; i<nextNumPointers; i++) {
+                conflicts |= assignPointer(i, true);
+            }
+            
+            // Resolve ambiguities in pointer mappings, when two or more
+            // new pointer locations find their best previous location is
+            // the same.
+            if (conflicts) {
+                if (DEBUG_POINTERS) Log.v("InputDevice", "Resolving conflicts");
+                
+                for (int i=0; i<lastNumPointers; i++) {
+                    if (last2Next[i] != -2) {
+                        continue;
+                    }
+                    
+                    // Note that this algorithm is far from perfect.  Ideally
+                    // we should do something like the one described at
+                    // http://portal.acm.org/citation.cfm?id=997856
+                    
+                    if (DEBUG_POINTERS) Log.v("InputDevice",
+                            "Resolving last index #" + i);
+                    
+                    int numFound;
+                    do {
+                        numFound = 0;
+                        long worstDistance = 0;
+                        int worstJ = -1;
+                        for (int j=0; j<nextNumPointers; j++) {
+                            if (next2Last[j] != i) {
+                                continue;
+                            }
+                            numFound++;
+                            if (worstDistance < next2LastDistance[j]) {
+                                worstDistance = next2LastDistance[j];
+                                worstJ = j;
+                            }
+                        }
+                        
+                        if (worstJ >= 0) {
+                            if (DEBUG_POINTERS) Log.v("InputDevice",
+                                    "Worst new pointer: " + worstJ
+                                    + " (distance=" + worstDistance + ")");
+                            if (assignPointer(worstJ, false)) {
+                                // In this case there is no last pointer
+                                // remaining for this new one!
+                                next2Last[worstJ] = -1;
+                            }
+                        }
+                    } while (numFound > 2);
+                }
+            }
+            
+            int retIndex = -1;
+            
+            if (lastNumPointers < nextNumPointers) {
+                // We have one or more new pointers that are down.  Create a
+                // new pointer identifier for one of them.
+                if (DEBUG_POINTERS) Log.v("InputDevice", "Adding new pointer");
+                int nextId = 0;
+                int i=0;
+                while (i < lastNumPointers) {
+                    if (mPointerIds[i] > nextId) {
+                        // Found a hole, insert the pointer here.
+                        if (DEBUG_POINTERS) Log.v("InputDevice",
+                                "Inserting new pointer at hole " + i);
+                        System.arraycopy(mPointerIds, i, mPointerIds,
+                                i+1, lastNumPointers-i);
+                        System.arraycopy(lastData, i*MotionEvent.NUM_SAMPLE_DATA,
+                                lastData, (i+1)*MotionEvent.NUM_SAMPLE_DATA,
+                                (lastNumPointers-i)*MotionEvent.NUM_SAMPLE_DATA);
+                        break;
+                    }
+                    i++;
+                    nextId++;
+                }
+                
+                if (DEBUG_POINTERS) Log.v("InputDevice",
+                        "New pointer id " + nextId + " at index " + i);
+                
+                mLastNumPointers++;
+                retIndex = i;
+                mPointerIds[i] = nextId;
+                
+                // And assign this identifier to the first new pointer.
+                for (int j=0; j<nextNumPointers; j++) {
+                    if (next2Last[j] < 0) {
+                        if (DEBUG_POINTERS) Log.v("InputDevice",
+                                "Assigning new id to new pointer index " + j);
+                        next2Last[j] = i;
+                        break;
+                    }
+                }
+            }
+            
+            // Propagate all of the current data into the appropriate
+            // location in the old data to match the pointer ID that was
+            // assigned to it.
+            for (int i=0; i<nextNumPointers; i++) {
+                int lastIndex = next2Last[i];
+                if (lastIndex >= 0) {
+                    if (DEBUG_POINTERS) Log.v("InputDevice",
+                            "Copying next pointer index " + i
+                            + " to last index " + lastIndex);
+                    System.arraycopy(nextData, i*MotionEvent.NUM_SAMPLE_DATA,
+                            lastData, lastIndex*MotionEvent.NUM_SAMPLE_DATA,
+                            MotionEvent.NUM_SAMPLE_DATA);
+                }
+            }
+            
+            if (lastNumPointers > nextNumPointers) {
+                // One or more pointers has gone up.  Find the first one,
+                // and adjust accordingly.
+                if (DEBUG_POINTERS) Log.v("InputDevice", "Removing old pointer");
+                for (int i=0; i<lastNumPointers; i++) {
+                    if (last2Next[i] == -1) {
+                        if (DEBUG_POINTERS) Log.v("InputDevice",
+                                "Removing old pointer at index " + i);
+                        retIndex = i;
+                        break;
+                    }
+                }
+            }
+            
+            return retIndex;
+        }
+        
+        void removeOldPointer(int index) {
+            final int lastNumPointers = mLastNumPointers;
+            if (index >= 0 && index < lastNumPointers) {
+                System.arraycopy(mPointerIds, index+1, mPointerIds,
+                        index, lastNumPointers-index-1);
+                System.arraycopy(mLastData, (index+1)*MotionEvent.NUM_SAMPLE_DATA,
+                        mLastData, (index)*MotionEvent.NUM_SAMPLE_DATA,
+                        (lastNumPointers-index-1)*MotionEvent.NUM_SAMPLE_DATA);
+                mLastNumPointers--;
+            }
         }
         
         MotionEvent generateAbsMotion(InputDevice device, long curTime,
                 long curTimeNano, Display display, int orientation,
                 int metaState) {
             
-            final float[] scaled = mReportData;
-            final int[] cur = mCurData;
-            
-            boolean anyDown = false;
-            int firstDownChanged = -1;
-            int numPointers = 0;
-            for (int i=0; i<MAX_POINTERS; i++) {
-                boolean d = mDown[i];
-                anyDown |= d;
-                if (d != mLastDown[i] && firstDownChanged < 0) {
-                    firstDownChanged = i;
-                    mLastDown[i] = mDown[i];
-                    d = true;
-                }
-                
-                if (d) {
-                    final int src = i * MotionEvent.NUM_SAMPLE_DATA;
-                    final int dest = numPointers * MotionEvent.NUM_SAMPLE_DATA;
-                    numPointers++;
-                    scaled[dest + MotionEvent.SAMPLE_X] = cur[src + MotionEvent.SAMPLE_X];
-                    scaled[dest + MotionEvent.SAMPLE_Y] = cur[src + MotionEvent.SAMPLE_Y];
-                    scaled[dest + MotionEvent.SAMPLE_PRESSURE] = cur[src + MotionEvent.SAMPLE_PRESSURE];
-                    scaled[dest + MotionEvent.SAMPLE_SIZE] = cur[src + MotionEvent.SAMPLE_SIZE];
-                }
+            if (mNextNumPointers <= 0 && mLastNumPointers <= 0) {
+                return null;
             }
             
-            if (numPointers <= 0) {
-                return null;
+            final int lastNumPointers = mLastNumPointers;
+            final int nextNumPointers = mNextNumPointers;
+            if (mNextNumPointers > MAX_POINTERS) {
+                Log.w("InputDevice", "Number of pointers " + mNextNumPointers
+                        + " exceeded maximum of " + MAX_POINTERS);
+                mNextNumPointers = MAX_POINTERS;
+            }
+            
+            int upOrDownPointer = updatePointerIdentifiers();
+            
+            final float[] reportData = mReportData;
+            final int[] rawData = mLastData;
+            
+            final int numPointers = mLastNumPointers;
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice", "Processing "
+                    + numPointers + " pointers (going from " + lastNumPointers
+                    + " to " + nextNumPointers + ")");
+            
+            for (int i=0; i<numPointers; i++) {
+                final int pos = i * MotionEvent.NUM_SAMPLE_DATA;
+                reportData[pos + MotionEvent.SAMPLE_X] = rawData[pos + MotionEvent.SAMPLE_X];
+                reportData[pos + MotionEvent.SAMPLE_Y] = rawData[pos + MotionEvent.SAMPLE_Y];
+                reportData[pos + MotionEvent.SAMPLE_PRESSURE] = rawData[pos + MotionEvent.SAMPLE_PRESSURE];
+                reportData[pos + MotionEvent.SAMPLE_SIZE] = rawData[pos + MotionEvent.SAMPLE_SIZE];
             }
             
             int action;
             int edgeFlags = 0;
-            if (anyDown != mLastAnyDown) {
-                final AbsoluteInfo absX = device.absX;
-                final AbsoluteInfo absY = device.absY;
-                if (anyDown && absX != null && absY != null) {
-                    // We don't let downs start unless we are
-                    // inside of the screen.  There are two reasons for
-                    // this: to avoid spurious touches when holding
-                    // the edges of the device near the touchscreen,
-                    // and to avoid reporting events if there are virtual
-                    // keys on the touchscreen outside of the display
-                    // area.
-                    // Note that we are only looking at the first pointer,
-                    // since what we are handling here is the first pointer
-                    // going down, and this is the coordinate that will be
-                    // used to dispatch the event.
-                    if (cur[MotionEvent.SAMPLE_X] < absX.minValue
-                            || cur[MotionEvent.SAMPLE_X] > absX.maxValue
-                            || cur[MotionEvent.SAMPLE_Y] < absY.minValue
-                            || cur[MotionEvent.SAMPLE_Y] > absY.maxValue) {
-                        if (false) Log.v("InputDevice", "Rejecting ("
-                                + cur[MotionEvent.SAMPLE_X] + ","
-                                + cur[MotionEvent.SAMPLE_Y] + "): outside of ("
-                                + absX.minValue + "," + absY.minValue
-                                + ")-(" + absX.maxValue + ","
-                                + absY.maxValue + ")");
-                        return null;
+            if (nextNumPointers != lastNumPointers) {
+                if (nextNumPointers > lastNumPointers) {
+                    if (lastNumPointers == 0) {
+                        action = MotionEvent.ACTION_DOWN;
+                        mDownTime = curTime;
+                    } else {
+                        action = MotionEvent.ACTION_POINTER_DOWN
+                                | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT);
                     }
-                }
-                mLastAnyDown = anyDown;
-                if (anyDown) {
-                    action = MotionEvent.ACTION_DOWN;
-                    mDownTime = curTime;
                 } else {
-                    action = MotionEvent.ACTION_UP;
-                }
-                currentMove = null;
-            } else if (firstDownChanged >= 0) {
-                if (mDown[firstDownChanged]) {
-                    action = MotionEvent.ACTION_POINTER_DOWN
-                            | (firstDownChanged << MotionEvent.ACTION_POINTER_SHIFT);
-                } else {
-                    action = MotionEvent.ACTION_POINTER_UP
-                            | (firstDownChanged << MotionEvent.ACTION_POINTER_SHIFT);
+                    if (numPointers == 1) {
+                        action = MotionEvent.ACTION_UP;
+                    } else {
+                        action = MotionEvent.ACTION_POINTER_UP
+                                | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT);
+                    }
                 }
                 currentMove = null;
             } else {
@@ -170,42 +388,42 @@
                 final int j = i * MotionEvent.NUM_SAMPLE_DATA;
             
                 if (absX != null) {
-                    scaled[j + MotionEvent.SAMPLE_X] =
-                            ((scaled[j + MotionEvent.SAMPLE_X]-absX.minValue)
+                    reportData[j + MotionEvent.SAMPLE_X] =
+                            ((reportData[j + MotionEvent.SAMPLE_X]-absX.minValue)
                                 / absX.range) * w;
                 }
                 if (absY != null) {
-                    scaled[j + MotionEvent.SAMPLE_Y] =
-                            ((scaled[j + MotionEvent.SAMPLE_Y]-absY.minValue)
+                    reportData[j + MotionEvent.SAMPLE_Y] =
+                            ((reportData[j + MotionEvent.SAMPLE_Y]-absY.minValue)
                                 / absY.range) * h;
                 }
                 if (absPressure != null) {
-                    scaled[j + MotionEvent.SAMPLE_PRESSURE] = 
-                            ((scaled[j + MotionEvent.SAMPLE_PRESSURE]-absPressure.minValue)
+                    reportData[j + MotionEvent.SAMPLE_PRESSURE] = 
+                            ((reportData[j + MotionEvent.SAMPLE_PRESSURE]-absPressure.minValue)
                                 / (float)absPressure.range);
                 }
                 if (absSize != null) {
-                    scaled[j + MotionEvent.SAMPLE_SIZE] = 
-                            ((scaled[j + MotionEvent.SAMPLE_SIZE]-absSize.minValue)
+                    reportData[j + MotionEvent.SAMPLE_SIZE] = 
+                            ((reportData[j + MotionEvent.SAMPLE_SIZE]-absSize.minValue)
                                 / (float)absSize.range);
                 }
                 
                 switch (orientation) {
                     case Surface.ROTATION_90: {
-                        final float temp = scaled[MotionEvent.SAMPLE_X];
-                        scaled[j + MotionEvent.SAMPLE_X] = scaled[j + MotionEvent.SAMPLE_Y];
-                        scaled[j + MotionEvent.SAMPLE_Y] = w-temp;
+                        final float temp = reportData[MotionEvent.SAMPLE_X];
+                        reportData[j + MotionEvent.SAMPLE_X] = reportData[j + MotionEvent.SAMPLE_Y];
+                        reportData[j + MotionEvent.SAMPLE_Y] = w-temp;
                         break;
                     }
                     case Surface.ROTATION_180: {
-                        scaled[j + MotionEvent.SAMPLE_X] = w-scaled[j + MotionEvent.SAMPLE_X];
-                        scaled[j + MotionEvent.SAMPLE_Y] = h-scaled[j + MotionEvent.SAMPLE_Y];
+                        reportData[j + MotionEvent.SAMPLE_X] = w-reportData[j + MotionEvent.SAMPLE_X];
+                        reportData[j + MotionEvent.SAMPLE_Y] = h-reportData[j + MotionEvent.SAMPLE_Y];
                         break;
                     }
                     case Surface.ROTATION_270: {
-                        final float temp = scaled[i + MotionEvent.SAMPLE_X];
-                        scaled[j + MotionEvent.SAMPLE_X] = h-scaled[j + MotionEvent.SAMPLE_Y];
-                        scaled[j + MotionEvent.SAMPLE_Y] = temp;
+                        final float temp = reportData[i + MotionEvent.SAMPLE_X];
+                        reportData[j + MotionEvent.SAMPLE_X] = h-reportData[j + MotionEvent.SAMPLE_Y];
+                        reportData[j + MotionEvent.SAMPLE_Y] = temp;
                         break;
                     }
                 }
@@ -214,24 +432,24 @@
             // We only consider the first pointer when computing the edge
             // flags, since they are global to the event.
             if (action == MotionEvent.ACTION_DOWN) {
-                if (scaled[MotionEvent.SAMPLE_X] <= 0) {
+                if (reportData[MotionEvent.SAMPLE_X] <= 0) {
                     edgeFlags |= MotionEvent.EDGE_LEFT;
-                } else if (scaled[MotionEvent.SAMPLE_X] >= dispW) {
+                } else if (reportData[MotionEvent.SAMPLE_X] >= dispW) {
                     edgeFlags |= MotionEvent.EDGE_RIGHT;
                 }
-                if (scaled[MotionEvent.SAMPLE_Y] <= 0) {
+                if (reportData[MotionEvent.SAMPLE_Y] <= 0) {
                     edgeFlags |= MotionEvent.EDGE_TOP;
-                } else if (scaled[MotionEvent.SAMPLE_Y] >= dispH) {
+                } else if (reportData[MotionEvent.SAMPLE_Y] >= dispH) {
                     edgeFlags |= MotionEvent.EDGE_BOTTOM;
                 }
             }
             
             if (currentMove != null) {
                 if (false) Log.i("InputDevice", "Adding batch x="
-                        + scaled[MotionEvent.SAMPLE_X]
-                        + " y=" + scaled[MotionEvent.SAMPLE_Y]
+                        + reportData[MotionEvent.SAMPLE_X]
+                        + " y=" + reportData[MotionEvent.SAMPLE_Y]
                         + " to " + currentMove);
-                currentMove.addBatch(curTime, scaled, metaState);
+                currentMove.addBatch(curTime, reportData, metaState);
                 if (WindowManagerPolicy.WATCH_POINTER) {
                     Log.i("KeyInputQueue", "Updating: " + currentMove);
                 }
@@ -239,37 +457,53 @@
             }
             
             MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime,
-                    curTimeNano, action, numPointers, scaled, metaState,
-                    xPrecision, yPrecision, device.id, edgeFlags);
+                    curTimeNano, action, numPointers, mPointerIds, reportData,
+                    metaState, xPrecision, yPrecision, device.id, edgeFlags);
             if (action == MotionEvent.ACTION_MOVE) {
                 currentMove = me;
             }
+            
+            if (nextNumPointers < lastNumPointers) {
+                removeOldPointer(upOrDownPointer);
+            }
+            
             return me;
         }
         
+        boolean hasMore() {
+            return mLastNumPointers != mNextNumPointers;
+        }
+        
+        void finish() {
+            mNextNumPointers = mAddingPointerOffset = 0;
+            mNextData[MotionEvent.SAMPLE_PRESSURE] = 0;
+        }
+        
         MotionEvent generateRelMotion(InputDevice device, long curTime,
                 long curTimeNano, int orientation, int metaState) {
             
             final float[] scaled = mReportData;
             
             // For now we only support 1 pointer with relative motions.
-            scaled[MotionEvent.SAMPLE_X] = mCurData[MotionEvent.SAMPLE_X];
-            scaled[MotionEvent.SAMPLE_Y] = mCurData[MotionEvent.SAMPLE_Y];
+            scaled[MotionEvent.SAMPLE_X] = mNextData[MotionEvent.SAMPLE_X];
+            scaled[MotionEvent.SAMPLE_Y] = mNextData[MotionEvent.SAMPLE_Y];
             scaled[MotionEvent.SAMPLE_PRESSURE] = 1.0f;
             scaled[MotionEvent.SAMPLE_SIZE] = 0;
             int edgeFlags = 0;
             
             int action;
-            if (mDown[0] != mLastDown[0]) {
-                mCurData[MotionEvent.SAMPLE_X] =
-                        mCurData[MotionEvent.SAMPLE_Y] = 0;
-                mLastDown[0] = mDown[0];
-                if (mDown[0]) {
+            if (mNextNumPointers != mLastNumPointers) {
+                mNextData[MotionEvent.SAMPLE_X] =
+                        mNextData[MotionEvent.SAMPLE_Y] = 0;
+                if (mNextNumPointers > 0 && mLastNumPointers == 0) {
                     action = MotionEvent.ACTION_DOWN;
                     mDownTime = curTime;
-                } else {
+                } else if (mNextNumPointers == 0) {
                     action = MotionEvent.ACTION_UP;
+                } else {
+                    action = MotionEvent.ACTION_MOVE;
                 }
+                mLastNumPointers = mNextNumPointers;
                 currentMove = null;
             } else {
                 action = MotionEvent.ACTION_MOVE;
@@ -310,7 +544,7 @@
             }
             
             MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime,
-                    curTimeNano, action, 1, scaled, metaState,
+                    curTimeNano, action, 1, mPointerIds, scaled, metaState,
                     xPrecision, yPrecision, device.id, edgeFlags);
             if (action == MotionEvent.ACTION_MOVE) {
                 currentMove = me;
diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index e0ee7ed..cfb3e35 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -49,6 +49,7 @@
     static final String TAG = "KeyInputQueue";
 
     static final boolean DEBUG_VIRTUAL_KEYS = false;
+    static final boolean DEBUG_POINTERS = false;
     
     private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
 
@@ -326,6 +327,10 @@
                         config.navigation
                                 = Configuration.NAVIGATION_TRACKBALL;
                         //Log.i("foo", "***** HAVE TRACKBALL!");
+                    } else if ((d.classes&RawInputEvent.CLASS_DPAD) != 0) {
+                        config.navigation
+                                = Configuration.NAVIGATION_DPAD;
+                        //Log.i("foo", "***** HAVE DPAD!");
                     }
                 }
             }
@@ -364,9 +369,9 @@
             android.os.Process.setThreadPriority(
                     android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
             
-            try {
-                RawInputEvent ev = new RawInputEvent();
-                while (true) {
+            RawInputEvent ev = new RawInputEvent();
+            while (true) {
+                try {
                     InputDevice di;
 
                     // block, doesn't release the monitor
@@ -465,49 +470,81 @@
                                              ? KeyEvent.FLAG_WOKE_HERE : 0));
                         } else if (ev.type == RawInputEvent.EV_KEY) {
                             if (ev.scancode == RawInputEvent.BTN_TOUCH &&
-                                    (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
+                                    (classes&(RawInputEvent.CLASS_TOUCHSCREEN
+                                            |RawInputEvent.CLASS_TOUCHSCREEN_MT))
+                                            == RawInputEvent.CLASS_TOUCHSCREEN) {
                                 di.mAbs.changed = true;
                                 di.mAbs.mDown[0] = ev.value != 0;
                             } else if (ev.scancode == RawInputEvent.BTN_2 &&
-                                    (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
+                                    (classes&(RawInputEvent.CLASS_TOUCHSCREEN
+                                            |RawInputEvent.CLASS_TOUCHSCREEN_MT))
+                                            == RawInputEvent.CLASS_TOUCHSCREEN) {
                                 di.mAbs.changed = true;
                                 di.mAbs.mDown[1] = ev.value != 0;
                             } else if (ev.scancode == RawInputEvent.BTN_MOUSE &&
                                     (classes&RawInputEvent.CLASS_TRACKBALL) != 0) {
                                 di.mRel.changed = true;
-                                di.mRel.mDown[0] = ev.value != 0;
+                                di.mRel.mNextNumPointers = ev.value != 0 ? 1 : 0;
                                 send = true;
                             }
     
                         } else if (ev.type == RawInputEvent.EV_ABS &&
+                                (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
+                            if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) {
+                                di.mAbs.changed = true;
+                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                        + MotionEvent.SAMPLE_PRESSURE] = ev.value;
+                            } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) {
+                                di.mAbs.changed = true;
+                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                    + MotionEvent.SAMPLE_X] = ev.value;
+                                if (DEBUG_POINTERS) Log.v(TAG, "MT @"
+                                        + di.mAbs.mAddingPointerOffset
+                                        + " X:" + ev.value);
+                            } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) {
+                                di.mAbs.changed = true;
+                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                    + MotionEvent.SAMPLE_Y] = ev.value;
+                                if (DEBUG_POINTERS) Log.v(TAG, "MT @"
+                                        + di.mAbs.mAddingPointerOffset
+                                        + " Y:" + ev.value);
+                            } else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) {
+                                di.mAbs.changed = true;
+                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                    + MotionEvent.SAMPLE_SIZE] = ev.value;
+                            }
+                            
+                        } else if (ev.type == RawInputEvent.EV_ABS &&
                                 (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
                             // Finger 1
                             if (ev.scancode == RawInputEvent.ABS_X) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.SAMPLE_X] = ev.value;
+                                di.mAbs.mNextData[MotionEvent.SAMPLE_X] = ev.value;
                             } else if (ev.scancode == RawInputEvent.ABS_Y) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.SAMPLE_Y] = ev.value;
+                                di.mAbs.mNextData[MotionEvent.SAMPLE_Y] = ev.value;
                             } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.SAMPLE_PRESSURE] = ev.value;
-                                di.mAbs.mCurData[MotionEvent.NUM_SAMPLE_DATA
+                                di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] = ev.value;
+                                di.mAbs.mNextData[MotionEvent.NUM_SAMPLE_DATA
                                                  + MotionEvent.SAMPLE_PRESSURE] = ev.value;
                             } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.SAMPLE_SIZE] = ev.value;
-                                di.mAbs.mCurData[MotionEvent.NUM_SAMPLE_DATA
+                                di.mAbs.mNextData[MotionEvent.SAMPLE_SIZE] = ev.value;
+                                di.mAbs.mNextData[MotionEvent.NUM_SAMPLE_DATA
                                                  + MotionEvent.SAMPLE_SIZE] = ev.value;
 
                             // Finger 2
                             } else if (ev.scancode == RawInputEvent.ABS_HAT0X) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.NUM_SAMPLE_DATA
-                                                 + MotionEvent.SAMPLE_X] = ev.value;
+                                di.mAbs.mNextData[(di.mAbs.mDown[0] ?
+                                        MotionEvent.NUM_SAMPLE_DATA : 0)
+                                         + MotionEvent.SAMPLE_X] = ev.value;
                             } else if (ev.scancode == RawInputEvent.ABS_HAT0Y) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.NUM_SAMPLE_DATA
-                                                 + MotionEvent.SAMPLE_Y] = ev.value;
+                                di.mAbs.mNextData[(di.mAbs.mDown[0] ?
+                                        MotionEvent.NUM_SAMPLE_DATA : 0)
+                                        + MotionEvent.SAMPLE_Y] = ev.value;
                             }
     
                         } else if (ev.type == RawInputEvent.EV_REL &&
@@ -515,14 +552,40 @@
                             // Add this relative movement into our totals.
                             if (ev.scancode == RawInputEvent.REL_X) {
                                 di.mRel.changed = true;
-                                di.mRel.mCurData[MotionEvent.SAMPLE_X] += ev.value;
+                                di.mRel.mNextData[MotionEvent.SAMPLE_X] += ev.value;
                             } else if (ev.scancode == RawInputEvent.REL_Y) {
                                 di.mRel.changed = true;
-                                di.mRel.mCurData[MotionEvent.SAMPLE_Y] += ev.value;
+                                di.mRel.mNextData[MotionEvent.SAMPLE_Y] += ev.value;
                             }
                         }
                         
-                        if (send || ev.type == RawInputEvent.EV_SYN) {
+                        if (ev.type == RawInputEvent.EV_SYN
+                                && ev.scancode == RawInputEvent.SYN_MT_REPORT
+                                && di.mAbs != null) {
+                            di.mAbs.changed = true;
+                            if (di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] > 0) {
+                                // If the value is <= 0, the pointer is not
+                                // down, so keep it in the count.
+                                
+                                if (di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                                      + MotionEvent.SAMPLE_PRESSURE] != 0) {
+                                    final int num = di.mAbs.mNextNumPointers+1;
+                                    di.mAbs.mNextNumPointers = num;
+                                    if (DEBUG_POINTERS) Log.v(TAG,
+                                            "MT_REPORT: now have " + num + " pointers");
+                                    final int newOffset = (num <= InputDevice.MAX_POINTERS)
+                                            ? (num * MotionEvent.NUM_SAMPLE_DATA)
+                                            : (InputDevice.MAX_POINTERS *
+                                                    MotionEvent.NUM_SAMPLE_DATA);
+                                    di.mAbs.mAddingPointerOffset = newOffset;
+                                    di.mAbs.mNextData[newOffset
+                                            + MotionEvent.SAMPLE_PRESSURE] = 0;
+                                } else {
+                                    if (DEBUG_POINTERS) Log.v(TAG, "MT_REPORT: no pointer");
+                                }
+                            }
+                        } else if (send || (ev.type == RawInputEvent.EV_SYN
+                                && ev.scancode == RawInputEvent.SYN_REPORT)) {
                             if (mDisplay != null) {
                                 if (!mHaveGlobalMetaState) {
                                     computeGlobalMetaStateLocked();
@@ -534,72 +597,21 @@
                                 if (ms.changed) {
                                     ms.changed = false;
                                     
-                                    boolean doMotion = true;
-                                    
-                                    // Look for virtual buttons.
-                                    VirtualKey vk = mPressedVirtualKey;
-                                    if (vk != null) {
-                                        doMotion = false;
-                                        if (!ms.mDown[0]) {
-                                            mPressedVirtualKey = null;
-                                            ms.mLastDown[0] = ms.mDown[0];
-                                            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
-                                                    "Generate key up for: " + vk.scancode);
-                                            KeyEvent event = newKeyEvent(di,
-                                                    di.mKeyDownTime, curTime, false,
-                                                    vk.lastKeycode,
-                                                    0, vk.scancode,
-                                                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
-                                            mHapticFeedbackCallback.virtualKeyFeedback(event);
-                                            addLocked(di, curTimeNano, ev.flags,
-                                                    RawInputEvent.CLASS_KEYBOARD,
-                                                    event);
-                                        } else if (isInsideDisplay(di)) {
-                                            // Whoops the pointer has moved into
-                                            // the display area!  Cancel the
-                                            // virtual key and start a pointer
-                                            // motion.
-                                            mPressedVirtualKey = null;
-                                            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
-                                                    "Cancel key up for: " + vk.scancode);
-                                            KeyEvent event = newKeyEvent(di,
-                                                    di.mKeyDownTime, curTime, false,
-                                                    vk.lastKeycode,
-                                                    0, vk.scancode,
-                                                    KeyEvent.FLAG_CANCELED |
-                                                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
-                                            mHapticFeedbackCallback.virtualKeyFeedback(event);
-                                            addLocked(di, curTimeNano, ev.flags,
-                                                    RawInputEvent.CLASS_KEYBOARD,
-                                                    event);
-                                            doMotion = true;
-                                            for (int i=InputDevice.MAX_POINTERS-1; i>=0; i--) {
-                                                ms.mLastDown[i] = false;
-                                            }
-                                        }
+                                    if ((classes&(RawInputEvent.CLASS_TOUCHSCREEN
+                                            |RawInputEvent.CLASS_TOUCHSCREEN_MT))
+                                            == RawInputEvent.CLASS_TOUCHSCREEN) {
+                                        ms.mNextNumPointers = 0;
+                                        if (ms.mDown[0]) ms.mNextNumPointers++;
+                                        if (ms.mDown[1]) ms.mNextNumPointers++;
                                     }
-                                    if (doMotion && ms.mDown[0] && !ms.mLastDown[0]) {
-                                        vk = findSoftButton(di);
-                                        if (vk != null) {
-                                            doMotion = false;
-                                            mPressedVirtualKey = vk;
-                                            vk.lastKeycode = scancodeToKeycode(
-                                                    di.id, vk.scancode);
-                                            ms.mLastDown[0] = ms.mDown[0];
-                                            di.mKeyDownTime = curTime;
-                                            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
-                                                    "Generate key down for: " + vk.scancode
-                                                    + " (keycode=" + vk.lastKeycode + ")");
-                                            KeyEvent event = newKeyEvent(di,
-                                                    di.mKeyDownTime, curTime, true,
-                                                    vk.lastKeycode, 0,
-                                                    vk.scancode,
-                                                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
-                                            mHapticFeedbackCallback.virtualKeyFeedback(event);
-                                            addLocked(di, curTimeNano, ev.flags,
-                                                    RawInputEvent.CLASS_KEYBOARD,
-                                                    event);
-                                        }
+                                    
+                                    boolean doMotion = !monitorVirtualKey(di,
+                                            ev, curTime, curTimeNano);
+                                    
+                                    if (doMotion && ms.mNextNumPointers > 0
+                                            && ms.mLastNumPointers == 0) {
+                                        doMotion = !generateVirtualKeyDown(di,
+                                                ev, curTime, curTimeNano);
                                     }
                                     
                                     if (doMotion) {
@@ -607,22 +619,26 @@
                                         // multiple events here, for example
                                         // if two fingers change up/down state
                                         // at the same time.
-                                        me = ms.generateAbsMotion(di, curTime,
-                                                curTimeNano, mDisplay,
-                                                mOrientation, mGlobalMetaState);
-                                        if (false) Log.v(TAG, "Absolute: x="
-                                                + di.mAbs.mCurData[MotionEvent.SAMPLE_X]
-                                                + " y="
-                                                + di.mAbs.mCurData[MotionEvent.SAMPLE_Y]
-                                                + " ev=" + me);
-                                        if (me != null) {
-                                            if (WindowManagerPolicy.WATCH_POINTER) {
-                                                Log.i(TAG, "Enqueueing: " + me);
+                                        do {
+                                            me = ms.generateAbsMotion(di, curTime,
+                                                    curTimeNano, mDisplay,
+                                                    mOrientation, mGlobalMetaState);
+                                            if (false) Log.v(TAG, "Absolute: x="
+                                                    + di.mAbs.mNextData[MotionEvent.SAMPLE_X]
+                                                    + " y="
+                                                    + di.mAbs.mNextData[MotionEvent.SAMPLE_Y]
+                                                    + " ev=" + me);
+                                            if (me != null) {
+                                                if (WindowManagerPolicy.WATCH_POINTER) {
+                                                    Log.i(TAG, "Enqueueing: " + me);
+                                                }
+                                                addLocked(di, curTimeNano, ev.flags,
+                                                        RawInputEvent.CLASS_TOUCHSCREEN, me);
                                             }
-                                            addLocked(di, curTimeNano, ev.flags,
-                                                    RawInputEvent.CLASS_TOUCHSCREEN, me);
-                                        }
+                                        } while (ms.hasMore());
                                     }
+                                    
+                                    ms.finish();
                                 }
                                 
                                 ms = di.mRel;
@@ -633,22 +649,24 @@
                                             curTimeNano,
                                             mOrientation, mGlobalMetaState);
                                     if (false) Log.v(TAG, "Relative: x="
-                                            + di.mRel.mCurData[MotionEvent.SAMPLE_X]
+                                            + di.mRel.mNextData[MotionEvent.SAMPLE_X]
                                             + " y="
-                                            + di.mRel.mCurData[MotionEvent.SAMPLE_Y]
+                                            + di.mRel.mNextData[MotionEvent.SAMPLE_Y]
                                             + " ev=" + me);
                                     if (me != null) {
                                         addLocked(di, curTimeNano, ev.flags,
                                                 RawInputEvent.CLASS_TRACKBALL, me);
                                     }
+                                    
+                                    ms.finish();
                                 }
                             }
                         }
                     }
-                }
                 
-            } catch (RuntimeException exc) {
-                Log.e(TAG, "InputReaderThread uncaught exception", exc);
+                } catch (RuntimeException exc) {
+                    Log.e(TAG, "InputReaderThread uncaught exception", exc);
+                }
             }
         }
     };
@@ -661,13 +679,13 @@
             return true;
         }
         
-        if (absm.mCurData[MotionEvent.SAMPLE_X] >= absx.minValue
-                && absm.mCurData[MotionEvent.SAMPLE_X] <= absx.maxValue
-                && absm.mCurData[MotionEvent.SAMPLE_Y] >= absy.minValue
-                && absm.mCurData[MotionEvent.SAMPLE_Y] <= absy.maxValue) {
+        if (absm.mNextData[MotionEvent.SAMPLE_X] >= absx.minValue
+                && absm.mNextData[MotionEvent.SAMPLE_X] <= absx.maxValue
+                && absm.mNextData[MotionEvent.SAMPLE_Y] >= absy.minValue
+                && absm.mNextData[MotionEvent.SAMPLE_Y] <= absy.maxValue) {
             if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Input ("
-                    + absm.mCurData[MotionEvent.SAMPLE_X]
-                    + "," + absm.mCurData[MotionEvent.SAMPLE_Y]
+                    + absm.mNextData[MotionEvent.SAMPLE_X]
+                    + "," + absm.mNextData[MotionEvent.SAMPLE_Y]
                     + ") inside of display");
             return true;
         }
@@ -675,28 +693,24 @@
         return false;
     }
     
-    private VirtualKey findSoftButton(InputDevice dev) {
+    private VirtualKey findVirtualKey(InputDevice dev) {
         final int N = mVirtualKeys.size();
         if (N <= 0) {
             return null;
         }
         
-        if (isInsideDisplay(dev)) {
-            return null;
-        }
-        
         final InputDevice.MotionState absm = dev.mAbs;
         for (int i=0; i<N; i++) {
             VirtualKey sb = mVirtualKeys.get(i);
             sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight);
             if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit test ("
-                    + absm.mCurData[MotionEvent.SAMPLE_X] + ","
-                    + absm.mCurData[MotionEvent.SAMPLE_Y] + ") in code "
+                    + absm.mNextData[MotionEvent.SAMPLE_X] + ","
+                    + absm.mNextData[MotionEvent.SAMPLE_Y] + ") in code "
                     + sb.scancode + " - (" + sb.hitLeft
                     + "," + sb.hitTop + ")-(" + sb.hitRight + ","
                     + sb.hitBottom + ")");
-            if (sb.checkHit(absm.mCurData[MotionEvent.SAMPLE_X],
-                    absm.mCurData[MotionEvent.SAMPLE_Y])) {
+            if (sb.checkHit(absm.mNextData[MotionEvent.SAMPLE_X],
+                    absm.mNextData[MotionEvent.SAMPLE_Y])) {
                 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit!");
                 return sb;
             }
@@ -705,6 +719,97 @@
         return null;
     }
     
+    private boolean generateVirtualKeyDown(InputDevice di, RawInputEvent ev,
+            long curTime, long curTimeNano) {
+        if (isInsideDisplay(di)) {
+            // Didn't consume event.
+            return false;
+        }
+        
+        
+        VirtualKey vk = findVirtualKey(di);
+        if (vk != null) {
+            final InputDevice.MotionState ms = di.mAbs;
+            mPressedVirtualKey = vk;
+            vk.lastKeycode = scancodeToKeycode(di.id, vk.scancode);
+            ms.mLastNumPointers = ms.mNextNumPointers;
+            di.mKeyDownTime = curTime;
+            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
+                    "Generate key down for: " + vk.scancode
+                    + " (keycode=" + vk.lastKeycode + ")");
+            KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, true,
+                    vk.lastKeycode, 0, vk.scancode,
+                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
+            mHapticFeedbackCallback.virtualKeyFeedback(event);
+            addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD,
+                    event);
+        }
+        
+        // We always consume the event, even if we didn't
+        // generate a key event.  There are two reasons for
+        // this: to avoid spurious touches when holding
+        // the edges of the device near the touchscreen,
+        // and to avoid reporting events if there are virtual
+        // keys on the touchscreen outside of the display
+        // area.
+        // Note that for all of this we are only looking at the
+        // first pointer, since what we are handling here is the
+        // first pointer going down, and this is the coordinate
+        // that will be used to dispatch the event.
+        if (false) {
+            final InputDevice.AbsoluteInfo absx = di.absX;
+            final InputDevice.AbsoluteInfo absy = di.absY;
+            final InputDevice.MotionState absm = di.mAbs;
+            Log.v(TAG, "Rejecting ("
+                + absm.mNextData[MotionEvent.SAMPLE_X] + ","
+                + absm.mNextData[MotionEvent.SAMPLE_Y] + "): outside of ("
+                + absx.minValue + "," + absy.minValue
+                + ")-(" + absx.maxValue + ","
+                + absx.maxValue + ")");
+        }
+        return true;
+    }
+    
+    private boolean monitorVirtualKey(InputDevice di, RawInputEvent ev,
+            long curTime, long curTimeNano) {
+        VirtualKey vk = mPressedVirtualKey;
+        if (vk == null) {
+            return false;
+        }
+        
+        final InputDevice.MotionState ms = di.mAbs;
+        if (ms.mNextNumPointers <= 0) {
+            mPressedVirtualKey = null;
+            ms.mLastNumPointers = 0;
+            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Generate key up for: " + vk.scancode);
+            KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false,
+                    vk.lastKeycode, 0, vk.scancode,
+                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
+            mHapticFeedbackCallback.virtualKeyFeedback(event);
+            addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD,
+                    event);
+            return true;
+            
+        } else if (isInsideDisplay(di)) {
+            // Whoops the pointer has moved into
+            // the display area!  Cancel the
+            // virtual key and start a pointer
+            // motion.
+            mPressedVirtualKey = null;
+            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Cancel key up for: " + vk.scancode);
+            KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false,
+                    vk.lastKeycode, 0, vk.scancode,
+                    KeyEvent.FLAG_CANCELED | KeyEvent.FLAG_VIRTUAL_HARD_KEY);
+            mHapticFeedbackCallback.virtualKeyFeedback(event);
+            addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD,
+                    event);
+            ms.mLastNumPointers = 0;
+            return false;
+        }
+        
+        return true;
+    }
+    
     /**
      * Returns a new meta state for the given keys and old state.
      */
@@ -851,8 +956,8 @@
             if (ev.event == ev.inputDevice.mRel.currentMove) {
                 if (false) Log.i(TAG, "Detach rel " + ev.event);
                 ev.inputDevice.mRel.currentMove = null;
-                ev.inputDevice.mRel.mCurData[MotionEvent.SAMPLE_X] = 0;
-                ev.inputDevice.mRel.mCurData[MotionEvent.SAMPLE_Y] = 0;
+                ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_X] = 0;
+                ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_Y] = 0;
             }
             recycleLocked(ev);
         }
@@ -945,7 +1050,12 @@
         InputDevice.AbsoluteInfo absY;
         InputDevice.AbsoluteInfo absPressure;
         InputDevice.AbsoluteInfo absSize;
-        if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
+        if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
+            absX = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_POSITION_X, "X");
+            absY = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_POSITION_Y, "Y");
+            absPressure = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure");
+            absSize = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size");
+        } else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
             absX = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_X, "X");
             absY = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_Y, "Y");
             absPressure = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_PRESSURE, "Pressure");