Merge "InputDevice filtering for jumpy screens. Updated ScaleGestureDetector for framework deprecations."
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 8140d61..d8b6d1f 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -154,9 +154,8 @@
         boolean handled = true;
 
         if (!mGestureInProgress) {
-            if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
-                    action == MotionEvent.ACTION_POINTER_2_DOWN) &&
-                    event.getPointerCount() >= 2) {
+            switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_POINTER_DOWN: {
                 // We have a new multi-finger gesture
 
                 // as orientation can change, query the metrics in touch down
@@ -189,7 +188,7 @@
                 boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
                         || x1 > rightSlop || y1 > bottomSlop;
 
-                if(p0sloppy && p1sloppy) {
+                if (p0sloppy && p1sloppy) {
                     mFocusX = -1;
                     mFocusY = -1;
                     mSloppyGesture = true;
@@ -204,54 +203,61 @@
                 } else {
                     mGestureInProgress = mListener.onScaleBegin(this);
                 }
-            } else if (action == MotionEvent.ACTION_MOVE && mSloppyGesture) {
-                // Initiate sloppy gestures if we've moved outside of the slop area.
-                final float edgeSlop = mEdgeSlop;
-                final float rightSlop = mRightSlopEdge;
-                final float bottomSlop = mBottomSlopEdge;
-                final float x0 = event.getRawX();
-                final float y0 = event.getRawY();
-                final float x1 = getRawX(event, 1);
-                final float y1 = getRawY(event, 1);
+            }
+            break;
+            
+            case MotionEvent.ACTION_MOVE:
+                if (mSloppyGesture) {
+                    // Initiate sloppy gestures if we've moved outside of the slop area.
+                    final float edgeSlop = mEdgeSlop;
+                    final float rightSlop = mRightSlopEdge;
+                    final float bottomSlop = mBottomSlopEdge;
+                    final float x0 = event.getRawX();
+                    final float y0 = event.getRawY();
+                    final float x1 = getRawX(event, 1);
+                    final float y1 = getRawY(event, 1);
 
-                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
-                        || x0 > rightSlop || y0 > bottomSlop;
-                boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
-                        || x1 > rightSlop || y1 > bottomSlop;
+                    boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+                    || x0 > rightSlop || y0 > bottomSlop;
+                    boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+                    || x1 > rightSlop || y1 > bottomSlop;
 
-                if(p0sloppy && p1sloppy) {
-                    mFocusX = -1;
-                    mFocusY = -1;
-                } else if (p0sloppy) {
-                    mFocusX = event.getX(1);
-                    mFocusY = event.getY(1);
-                } else if (p1sloppy) {
-                    mFocusX = event.getX(0);
-                    mFocusY = event.getY(0);
-                } else {
-                    mSloppyGesture = false;
-                    mGestureInProgress = mListener.onScaleBegin(this);
+                    if(p0sloppy && p1sloppy) {
+                        mFocusX = -1;
+                        mFocusY = -1;
+                    } else if (p0sloppy) {
+                        mFocusX = event.getX(1);
+                        mFocusY = event.getY(1);
+                    } else if (p1sloppy) {
+                        mFocusX = event.getX(0);
+                        mFocusY = event.getY(0);
+                    } else {
+                        mSloppyGesture = false;
+                        mGestureInProgress = mListener.onScaleBegin(this);
+                    }
                 }
-            } else if ((action == MotionEvent.ACTION_POINTER_1_UP
-                    || action == MotionEvent.ACTION_POINTER_2_UP)
-                    && mSloppyGesture) {
-                // Set focus point to the remaining finger
-                int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
-                        >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
-                mFocusX = event.getX(id);
-                mFocusY = event.getY(id);
+                break;
+                
+            case MotionEvent.ACTION_POINTER_UP:
+                if (mSloppyGesture) {
+                    // Set focus point to the remaining finger
+                    int id = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                            >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
+                    mFocusX = event.getX(id);
+                    mFocusY = event.getY(id);
+                }
+                break;
             }
         } else {
             // Transform gesture in progress - attempt to handle it
-            switch (action) {
-                case MotionEvent.ACTION_POINTER_1_UP:
-                case MotionEvent.ACTION_POINTER_2_UP:
+            switch (action & MotionEvent.ACTION_MASK) {
+                case MotionEvent.ACTION_POINTER_UP:
                     // Gesture ended
                     setContext(event);
 
                     // Set focus point to the remaining finger
-                    int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
-                            >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+                    int id = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                            >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
                     mFocusX = event.getX(id);
                     mFocusY = event.getY(id);
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 02961f0..84cd4c3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -267,6 +267,11 @@
          it will be removed when the lower-level touch driver generates better
          data. -->
     <bool name="config_filterTouchEvents">false</bool>
+
+    <!-- Enables special filtering code in the framework for raw touch events
+         from the touch driver. This code exists for one particular device,
+         and should not be enabled for any others. -->
+    <bool name="config_filterJumpyTouchEvents">false</bool>
     
     <!-- Component name of the default wallpaper. This will be ImageWallpaper if not 
          specified -->
diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java
index d5e94ec..ed7eed0 100644
--- a/services/java/com/android/server/InputDevice.java
+++ b/services/java/com/android/server/InputDevice.java
@@ -34,6 +34,16 @@
     /** Maximum number of pointers we will track and report. */
     static final int MAX_POINTERS = 10;
     
+    /**
+     * Slop distance for jumpy pointer detection.
+     * This is in touchscreen coordinates, not pixels or dips.
+     */
+    private static final int JUMPY_EPSILON = 30;
+    
+    /** Number of jumpy points to drop for touchscreens that need it. */
+    private static final int JUMPY_TRANSITION_DROPS = 3;
+    private static final int JUMPY_DROP_LIMIT = 3;
+    
     final int id;
     final int classes;
     final String name;
@@ -84,6 +94,9 @@
         // Used to determine whether we dropped bad data, to avoid doing
         // it repeatedly.
         final boolean[] mDroppedBadPoint = new boolean[MAX_POINTERS];
+
+        // Used to count the number of jumpy points dropped.
+        private int mJumpyPointsDropped = 0;
         
         // Used to perform averaging of reported coordinates, to smooth
         // the data and filter out transients during a release.
@@ -232,6 +245,158 @@
             }
         }
         
+        void dropJumpyPoint(InputDevice dev) {
+            final int nextNumPointers = mNextNumPointers;
+            final int lastNumPointers = mLastNumPointers;
+            final int[] nextData = mNextData;
+            final int[] lastData = mLastData;
+
+            if (nextNumPointers != mLastNumPointers) {
+                if (DEBUG_HACKS) {
+                    Slog.d("InputDevice", "Different pointer count " + lastNumPointers + 
+                            " -> " + nextNumPointers);
+                    for (int i = 0; i < nextNumPointers; i++) {
+                        int ioff = i * MotionEvent.NUM_SAMPLE_DATA;
+                        Slog.d("InputDevice", "Pointer " + i + " (" + 
+                                mNextData[ioff + MotionEvent.SAMPLE_X] + ", " +
+                                mNextData[ioff + MotionEvent.SAMPLE_Y] + ")");
+                    }
+                }
+                
+                // Just drop the first few events going from 1 to 2 pointers.
+                // They're bad often enough that they're not worth considering.
+                if (lastNumPointers == 1 && nextNumPointers == 2
+                        && mJumpyPointsDropped < JUMPY_TRANSITION_DROPS) {
+                    mNextNumPointers = 1;
+                    mJumpyPointsDropped++;
+                } else if (lastNumPointers == 2 && nextNumPointers == 1
+                        && mJumpyPointsDropped < JUMPY_TRANSITION_DROPS) {
+                    // The event when we go from 2 -> 1 tends to be messed up too
+                    System.arraycopy(lastData, 0, nextData, 0, 
+                            lastNumPointers * MotionEvent.NUM_SAMPLE_DATA);
+                    mNextNumPointers = lastNumPointers;
+                    mJumpyPointsDropped++;
+                    
+                    if (DEBUG_HACKS) {
+                        for (int i = 0; i < mNextNumPointers; i++) {
+                            int ioff = i * MotionEvent.NUM_SAMPLE_DATA;
+                            Slog.d("InputDevice", "Pointer " + i + " replaced (" + 
+                                    mNextData[ioff + MotionEvent.SAMPLE_X] + ", " +
+                                    mNextData[ioff + MotionEvent.SAMPLE_Y] + ")");
+                        }
+                    }
+                } else {
+                    mJumpyPointsDropped = 0;
+                    
+                    if (DEBUG_HACKS) {
+                        Slog.d("InputDevice", "Transition - drop limit reset");
+                    }
+                }
+                return;
+            }
+            
+            // A 'jumpy' point is one where the coordinate value for one axis
+            // has jumped to the other pointer's location. No need to do anything
+            // else if we only have one pointer.
+            if (nextNumPointers < 2) {
+                return;
+            }
+            
+            int badPointerIndex = -1;
+            int badPointerReplaceXWith = 0;
+            int badPointerReplaceYWith = 0;
+            int badPointerDistance = Integer.MIN_VALUE;
+            for (int i = nextNumPointers - 1; i >= 0; i--) {
+                boolean dropx = false;
+                boolean dropy = false;
+                
+                // Limit how many times a jumpy point can get dropped.
+                if (mJumpyPointsDropped < JUMPY_DROP_LIMIT) {
+                    final int ioff = i * MotionEvent.NUM_SAMPLE_DATA;
+                    final int x = nextData[ioff + MotionEvent.SAMPLE_X];
+                    final int y = nextData[ioff + MotionEvent.SAMPLE_Y];
+                    
+                    if (DEBUG_HACKS) {
+                        Slog.d("InputDevice", "Point " + i + " (" + x + ", " + y + ")");
+                    }
+
+                    // Check if a touch point is too close to another's coordinates
+                    for (int j = 0; j < nextNumPointers && !dropx && !dropy; j++) {
+                        if (j == i) {
+                            continue;
+                        }
+
+                        final int joff = j * MotionEvent.NUM_SAMPLE_DATA;
+                        final int xOther = nextData[joff + MotionEvent.SAMPLE_X];
+                        final int yOther = nextData[joff + MotionEvent.SAMPLE_Y];
+
+                        dropx = Math.abs(x - xOther) <= JUMPY_EPSILON;
+                        dropy = Math.abs(y - yOther) <= JUMPY_EPSILON;
+                    }
+                    
+                    if (dropx) {
+                        int xreplace = lastData[MotionEvent.SAMPLE_X];
+                        int yreplace = lastData[MotionEvent.SAMPLE_Y];
+                        int distance = Math.abs(yreplace - y);
+                        for (int j = 1; j < lastNumPointers; j++) {
+                            final int joff = j * MotionEvent.NUM_SAMPLE_DATA;
+                            int lasty = lastData[joff + MotionEvent.SAMPLE_Y];   
+                            int currDist = Math.abs(lasty - y);
+                            if (currDist < distance) {
+                                xreplace = lastData[joff + MotionEvent.SAMPLE_X];
+                                yreplace = lasty;
+                                distance = currDist;
+                            }
+                        }
+                        
+                        int badXDelta = Math.abs(xreplace - x);
+                        if (badXDelta > badPointerDistance) {
+                            badPointerDistance = badXDelta;
+                            badPointerIndex = i;
+                            badPointerReplaceXWith = xreplace;
+                            badPointerReplaceYWith = yreplace;
+                        }
+                    } else if (dropy) {
+                        int xreplace = lastData[MotionEvent.SAMPLE_X];
+                        int yreplace = lastData[MotionEvent.SAMPLE_Y];
+                        int distance = Math.abs(xreplace - x);
+                        for (int j = 1; j < lastNumPointers; j++) {
+                            final int joff = j * MotionEvent.NUM_SAMPLE_DATA;
+                            int lastx = lastData[joff + MotionEvent.SAMPLE_X];   
+                            int currDist = Math.abs(lastx - x);
+                            if (currDist < distance) {
+                                xreplace = lastx;
+                                yreplace = lastData[joff + MotionEvent.SAMPLE_Y];
+                                distance = currDist;
+                            }
+                        }
+                        
+                        int badYDelta = Math.abs(yreplace - y);
+                        if (badYDelta > badPointerDistance) {
+                            badPointerDistance = badYDelta;
+                            badPointerIndex = i;
+                            badPointerReplaceXWith = xreplace;
+                            badPointerReplaceYWith = yreplace;
+                        }
+                    }
+                }
+            }
+            if (badPointerIndex >= 0) {
+                if (DEBUG_HACKS) {
+                    Slog.d("InputDevice", "Replacing bad pointer " + badPointerIndex +
+                            " with (" + badPointerReplaceXWith + ", " + badPointerReplaceYWith +
+                            ")");
+                }
+
+                final int offset = badPointerIndex * MotionEvent.NUM_SAMPLE_DATA;
+                nextData[offset + MotionEvent.SAMPLE_X] = badPointerReplaceXWith;
+                nextData[offset + MotionEvent.SAMPLE_Y] = badPointerReplaceYWith;
+                mJumpyPointsDropped++;
+            } else {
+                mJumpyPointsDropped = 0;
+            }
+        }
+        
         /**
          * Special hack for devices that have bad screen data: aggregate and
          * compute averages of the coordinate data, to reduce the amount of
diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index a08258a..8cd9578 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.os.Environment;
 import android.os.LatencyTimer;
 import android.os.PowerManager;
@@ -60,6 +61,12 @@
      */
     static boolean BAD_TOUCH_HACK = false;
     
+    /**
+     * Turn on some hacks to improve touch interaction with another device
+     * where touch coordinate data can get corrupted.
+     */
+    static boolean JUMPY_TOUCH_HACK = false;
+    
     private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
 
     final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>();
@@ -284,8 +291,10 @@
             lt = new LatencyTimer(100, 1000);
         }
 
-        BAD_TOUCH_HACK = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_filterTouchEvents);
+        Resources r = context.getResources();
+        BAD_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterTouchEvents);
+        
+        JUMPY_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterJumpyTouchEvents);
         
         mHapticFeedbackCallback = hapticFeedbackCallback;
         
@@ -758,6 +767,9 @@
                                     if (BAD_TOUCH_HACK) {
                                         ms.dropBadPoint(di);
                                     }
+                                    if (JUMPY_TOUCH_HACK) {
+                                        ms.dropJumpyPoint(di);
+                                    }
                                     
                                     boolean doMotion = !monitorVirtualKey(di,
                                             ev, curTime, curTimeNano);