HorizontalScrollView multitouch scroll and only grip on content
AbsListView multitouch scroll and only grip on content
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 9ddfeff..a9b746a 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -467,6 +467,18 @@
     // True when the popup should be hidden because of a call to
     // dispatchDisplayHint()
     private boolean mPopupHidden;
+    
+    /**
+     * ID of the active pointer. This is used to retain consistency during
+     * drags/flings if multiple pointers are used.
+     */
+    private int mActivePointerId = INVALID_POINTER;
+    
+    /**
+     * Sentinel value for no current active pointer.
+     * Used by {@link #mActivePointerId}.
+     */
+    private static final int INVALID_POINTER = -1;
 
     /**
      * Interface definition for a callback to be invoked when the list or grid
@@ -1995,8 +2007,6 @@
         }
 
         final int action = ev.getAction();
-        final int x = (int) ev.getX();
-        final int y = (int) ev.getY();
 
         View v;
         int deltaY;
@@ -2006,18 +2016,22 @@
         }
         mVelocityTracker.addMovement(ev);
 
-        switch (action) {
+        switch (action & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN: {
             switch (mTouchMode) {
             case TOUCH_MODE_OVERFLING: {
                 mFlingRunnable.endFling();
                 mTouchMode = TOUCH_MODE_OVERSCROLL;
-                mLastY = y;
+                mLastY = (int) ev.getY();
                 mMotionCorrection = 0;
+                mActivePointerId = ev.getPointerId(0);
                 break;
             }
             
             default: {
+                mActivePointerId = ev.getPointerId(0);
+                final int x = (int) ev.getX();
+                final int y = (int) ev.getY();
                 int motionPosition = pointToPosition(x, y);
                 if (!mDataChanged) {
                     if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
@@ -2037,12 +2051,15 @@
                             // code in ViewRoot to try to find a nearby view to select
                             return false;
                         }
-                        // User clicked on whitespace, or stopped a fling. It is a scroll.
-                        createScrollingCache();
-                        mTouchMode = TOUCH_MODE_SCROLL;
-                        mMotionCorrection = 0;
-                        motionPosition = findMotionRow(y);
-                        reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+                        
+                        if (mTouchMode == TOUCH_MODE_FLING) {
+                            // Stopped a fling. It is a scroll.
+                            createScrollingCache();
+                            mTouchMode = TOUCH_MODE_SCROLL;
+                            mMotionCorrection = 0;
+                            motionPosition = findMotionRow(y);
+                            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+                        }
                     }
                 }
 
@@ -2062,6 +2079,8 @@
         }
 
         case MotionEvent.ACTION_MOVE: {
+            final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+            final int y = (int) ev.getY(pointerIndex);
             deltaY = y - mMotionY;
             switch (mTouchMode) {
             case TOUCH_MODE_DOWN:
@@ -2142,7 +2161,7 @@
 
                             // We did not scroll the full amount. Treat this essentially like the
                             // start of a new touch scroll
-                            final int motionPosition = findMotionRow(y);
+                            final int motionPosition = findClosestMotionRow(y);
 
                             mMotionCorrection = 0;
                             motionView = getChildAt(motionPosition - mFirstPosition);
@@ -2238,7 +2257,7 @@
                     } else {
                         final VelocityTracker velocityTracker = mVelocityTracker;
                         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                        final int initialVelocity = (int) velocityTracker.getYVelocity();
+                        final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
     
                         if (Math.abs(initialVelocity) > mMinimumVelocity) {
                             if (mFlingRunnable == null) {
@@ -2264,7 +2283,7 @@
                 }
                 final VelocityTracker velocityTracker = mVelocityTracker;
                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                final int initialVelocity = (int) velocityTracker.getYVelocity();
+                final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
 
                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
                 if (Math.abs(initialVelocity) > mMinimumVelocity) {
@@ -2290,6 +2309,8 @@
                 mVelocityTracker.recycle();
                 mVelocityTracker = null;
             }
+            
+            mActivePointerId = INVALID_POINTER;
 
             if (PROFILE_SCROLLING) {
                 if (mScrollProfilingStarted) {
@@ -2332,6 +2353,24 @@
                     mVelocityTracker = null;
                 }
             }
+            
+            mActivePointerId = INVALID_POINTER;
+            break;
+        }
+        
+        case MotionEvent.ACTION_POINTER_UP: {
+            onSecondaryPointerUp(ev);
+            final int x = mMotionX;
+            final int y = mMotionY;
+            final int motionPosition = pointToPosition(x, y);
+            if (motionPosition >= 0) {
+                // Remember where the motion event started
+                v = getChildAt(motionPosition - mFirstPosition);
+                mMotionViewOriginalTop = v.getTop();
+                mMotionPosition = motionPosition;
+            }
+            mLastY = y;
+            break;
         }
         }
 
@@ -2380,8 +2419,6 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         int action = ev.getAction();
-        int x = (int) ev.getX();
-        int y = (int) ev.getY();
         View v;
 
         if (mFastScroller != null) {
@@ -2391,13 +2428,17 @@
             }
         }
 
-        switch (action) {
+        switch (action & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN: {
             int touchMode = mTouchMode;
             if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
                 return true;
             }
             
+            final int x = (int) ev.getX();
+            final int y = (int) ev.getY();
+            mActivePointerId = ev.getPointerId(0);
+            
             int motionPosition = findMotionRow(y);
             if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
                 // User clicked on an actual view (and was not stopping a fling).
@@ -2420,6 +2461,8 @@
         case MotionEvent.ACTION_MOVE: {
             switch (mTouchMode) {
             case TOUCH_MODE_DOWN:
+                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                final int y = (int) ev.getY(pointerIndex);
                 if (startScrollIfNeeded(y - mMotionY)) {
                     return true;
                 }
@@ -2430,13 +2473,37 @@
 
         case MotionEvent.ACTION_UP: {
             mTouchMode = TOUCH_MODE_REST;
+            mActivePointerId = INVALID_POINTER;
             reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
             break;
         }
+        
+        case MotionEvent.ACTION_POINTER_UP: {
+            onSecondaryPointerUp(ev);
+            break;
+        }
         }
 
         return false;
     }
+    
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+        final int pointerId = ev.getPointerId(pointerIndex);
+        if (pointerId == mActivePointerId) {
+            // This was our active pointer going up. Choose a new
+            // active pointer and adjust accordingly.
+            // TODO: Make this decision more intelligent.
+            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+            mMotionX = (int) ev.getX(newPointerIndex);
+            mMotionY = (int) ev.getY(newPointerIndex);
+            mActivePointerId = ev.getPointerId(newPointerIndex);
+            if (mVelocityTracker != null) {
+                mVelocityTracker.clear();
+            }
+        }
+    }
 
     /**
      * {@inheritDoc}
@@ -3150,9 +3217,25 @@
      * Find the row closest to y. This row will be used as the motion row when scrolling
      *
      * @param y Where the user touched
-     * @return The position of the first (or only) item in the row closest to y
+     * @return The position of the first (or only) item in the row containing y
      */
     abstract int findMotionRow(int y);
+    
+    /**
+     * Find the row closest to y. This row will be used as the motion row when scrolling.
+     * 
+     * @param y Where the user touched
+     * @return The position of the first (or only) item in the row closest to y
+     */
+    int findClosestMotionRow(int y) {
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return INVALID_POSITION;
+        }
+        
+        final int motionRow = findMotionRow(y);
+        return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
+    }
 
     /**
      * Causes all the views to be rebuilt and redrawn.
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index b9acf5e..9e930a5 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -431,8 +431,6 @@
                     }
                 }
             }
-
-            return mFirstPosition + childCount - 1;
         }
         return INVALID_POSITION;
     }
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index a7b819a..acb7e02c 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -116,7 +116,19 @@
     private int mTouchSlop;
     private int mMinimumVelocity;
     private int mMaximumVelocity;
-
+    
+    /**
+     * ID of the active pointer. This is used to retain consistency during
+     * drags/flings if multiple pointers are used.
+     */
+    private int mActivePointerId = INVALID_POINTER;
+    
+    /**
+     * Sentinel value for no current active pointer.
+     * Used by {@link #mActivePointerId}.
+     */
+    private static final int INVALID_POINTER = -1;
+    
     public HorizontalScrollView(Context context) {
         this(context, null);
     }
@@ -362,6 +374,17 @@
         return handled;
     }
 
+    private boolean inChild(int x, int y) {
+        if (getChildCount() > 0) {
+            final View child = getChildAt(0);
+            return !(y < child.getTop()
+                    || y >= child.getBottom()
+                    || x < child.getLeft()
+                    || x >= child.getRight());
+        }
+        return false;
+    }
+    
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         /*
@@ -380,10 +403,8 @@
             return true;
         }
 
-        final float x = ev.getX();
-
-        switch (action) {
-            case MotionEvent.ACTION_MOVE:
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_MOVE: {
                 /*
                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                  * whether the user has moved far enough from his original down touch.
@@ -393,16 +414,30 @@
                 * Locally do absolute value. mLastMotionX is set to the x value
                 * of the down event.
                 */
+                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                final float x = ev.getX(pointerIndex);
                 final int xDiff = (int) Math.abs(x - mLastMotionX);
                 if (xDiff > mTouchSlop) {
                     mIsBeingDragged = true;
+                    mLastMotionX = x;
                     if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
                 }
                 break;
+            }
 
-            case MotionEvent.ACTION_DOWN:
-                /* Remember location of down touch */
+            case MotionEvent.ACTION_DOWN: {
+                final float x = ev.getX();
+                if (!inChild((int) x, (int) ev.getY())) {
+                    mIsBeingDragged = false;
+                    break;
+                }
+                
+                /*
+                 * Remember location of down touch.
+                 * ACTION_DOWN always refers to pointer index 0.
+                 */
                 mLastMotionX = x;
+                mActivePointerId = ev.getPointerId(0);
 
                 /*
                 * If being flinged and user touches the screen, initiate drag;
@@ -411,11 +446,16 @@
                 */
                 mIsBeingDragged = !mScroller.isFinished();
                 break;
+            }
 
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
                 /* Release the drag */
                 mIsBeingDragged = false;
+                mActivePointerId = INVALID_POINTER;
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
                 break;
         }
 
@@ -441,10 +481,9 @@
         mVelocityTracker.addMovement(ev);
 
         final int action = ev.getAction();
-        final float x = ev.getX();
 
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
                 /*
                 * If being flinged and user touches, stop the fling. isFinished
                 * will be false if being flinged.
@@ -452,42 +491,78 @@
                 if (!mScroller.isFinished()) {
                     mScroller.abortAnimation();
                 }
+                
+                final float x = ev.getX();
+                if (!(mIsBeingDragged = inChild((int) x, (int) ev.getY()))) {
+                    return false;
+                }
 
                 // Remember where the motion event started
                 mLastMotionX = x;
                 break;
+            }
             case MotionEvent.ACTION_MOVE:
-                // Scroll to follow the motion event
-                final int deltaX = (int) (mLastMotionX - x);
-                mLastMotionX = x;
+                if (mIsBeingDragged) {
+                    // Scroll to follow the motion event
+                    final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+                    final float x = ev.getX(activePointerIndex);
+                    final int deltaX = (int) (mLastMotionX - x);
+                    mLastMotionX = x;
 
-                overscrollBy(deltaX, 0, mScrollX, 0, getScrollRange(), 0,
-                        getOverscrollMax(), 0);
+                    overscrollBy(deltaX, 0, mScrollX, 0, getScrollRange(), 0,
+                            getOverscrollMax(), 0);
+                }
                 break;
             case MotionEvent.ACTION_UP:
-                final VelocityTracker velocityTracker = mVelocityTracker;
-                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                int initialVelocity = (int) velocityTracker.getXVelocity();
+                if (mIsBeingDragged) {
+                    final VelocityTracker velocityTracker = mVelocityTracker;
+                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                    int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
 
-                if (getChildCount() > 0) {
-                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
-                        fling(-initialVelocity);
-                    } else {
-                        final int right = getScrollRange();
-                        if (mScroller.springback(mScrollX, mScrollY, 0, 0, right, 0)) {
-                            invalidate();
+                    if (getChildCount() > 0) {
+                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+                            fling(-initialVelocity);
+                        } else {
+                            final int right = getScrollRange();
+                            if (mScroller.springback(mScrollX, mScrollY, 0, 0, right, 0)) {
+                                invalidate();
+                            }
                         }
                     }
-                }
+                    
+                    mActivePointerId = INVALID_POINTER;
+                    mIsBeingDragged = false;
 
-                if (mVelocityTracker != null) {
-                    mVelocityTracker.recycle();
-                    mVelocityTracker = null;
+                    if (mVelocityTracker != null) {
+                        mVelocityTracker.recycle();
+                        mVelocityTracker = null;
+                    }
                 }
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                break;
         }
         return true;
     }
     
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+        final int pointerId = ev.getPointerId(pointerIndex);
+        if (pointerId == mActivePointerId) {
+            // This was our active pointer going up. Choose a new
+            // active pointer and adjust accordingly.
+            // TODO: Make this decision more intelligent.
+            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+            mLastMotionX = ev.getX(newPointerIndex);
+            mActivePointerId = ev.getPointerId(newPointerIndex);
+            if (mVelocityTracker != null) {
+                mVelocityTracker.clear();
+            }
+        }
+    }
+    
     @Override
     protected void onOverscrolled(int scrollX, int scrollY,
             boolean clampedX, boolean clampedY) {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 8d688a5..51a1ef2 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1192,7 +1192,6 @@
                     return mFirstPosition + i;
                 }
             }
-            return mFirstPosition + childCount - 1;
         }
         return INVALID_POSITION;
     }
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index fd24058..489c44d 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -427,7 +427,7 @@
 
             case MotionEvent.ACTION_DOWN: {
                 final float y = ev.getY();
-                if (!inChild((int)ev.getX(), (int)y)) {
+                if (!inChild((int) ev.getX(), (int) y)) {
                     mIsBeingDragged = false;
                     break;
                 }
@@ -493,7 +493,7 @@
                 }
 
                 final float y = ev.getY();
-                if (!(mIsBeingDragged = inChild((int)ev.getX(), (int)y))) {
+                if (!(mIsBeingDragged = inChild((int) ev.getX(), (int) y))) {
                     return false;
                 }