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/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;