Merge "Text handles movement improvements."
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6791a1d..d5515b8 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8655,9 +8655,11 @@
         private float mTouchToWindowOffsetX;
         private float mTouchToWindowOffsetY;
         private float mHotspotX;
-        private float mHotspotY;
         private int mHeight;
+        // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up
         private float mTouchOffsetY;
+        // Where the touch position should be on the handle to ensure a maximum cursor visibility
+        private float mIdealVerticalOffset;
         private int mLastParentX;
         private int mLastParentY;
         private float mDownPositionX, mDownPositionY;
@@ -8767,7 +8769,7 @@
             final int handleHeight = mDrawable.getIntrinsicHeight();
 
             mTouchOffsetY = -handleHeight * 0.3f;
-            mHotspotY = 0;
+            mIdealVerticalOffset = 0.7f * handleHeight;
             mHeight = handleHeight;
             invalidate();
         }
@@ -8836,7 +8838,7 @@
             final int[] coords = mTempCoords;
             hostView.getLocationInWindow(coords);
             final int posX = coords[0] + mPositionX + (int) mHotspotX;
-            final int posY = coords[1] + mPositionY + (int) mHotspotY;
+            final int posY = coords[1] + mPositionY;
 
             // Offset by 1 to take into account 0.5 and int rounding around getPrimaryHorizontal.
             return posX >= clip.left - 1 && posX <= clip.right + 1 &&
@@ -8901,6 +8903,7 @@
                     mDownPositionY = ev.getRawY();
                     mTouchToWindowOffsetX = mDownPositionX - mPositionX;
                     mTouchToWindowOffsetY = mDownPositionY - mPositionY;
+
                     final int[] coords = mTempCoords;
                     TextView.this.getLocationInWindow(coords);
                     mLastParentX = coords[0];
@@ -8915,8 +8918,22 @@
                 case MotionEvent.ACTION_MOVE: {
                     final float rawX = ev.getRawX();
                     final float rawY = ev.getRawY();
+
+                    // Vertical hysteresis: vertical down movement tends to snap to ideal offset
+                    final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY;
+                    final float currentVerticalOffset = rawY - mPositionY - mLastParentY;
+                    float newVerticalOffset;
+                    if (previousVerticalOffset < mIdealVerticalOffset) {
+                        newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset);
+                        newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset);
+                    } else {
+                        newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset);
+                        newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset);
+                    }
+                    mTouchToWindowOffsetY = newVerticalOffset + mLastParentY;
+
                     final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
-                    final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY + mTouchOffsetY;
+                    final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY;
 
                     mController.updatePosition(this, Math.round(newPosX), Math.round(newPosY));
                     break;
@@ -8954,18 +8971,17 @@
             return mIsDragging;
         }
 
-        void positionAtCursor(final int offset, boolean bottom) {
+        void positionAtCursor(final int offset) {
             addPositionToTouchUpFilter(offset);
             final int width = mDrawable.getIntrinsicWidth();
             final int height = mDrawable.getIntrinsicHeight();
             final int line = mLayout.getLineForOffset(offset);
-            final int lineTop = mLayout.getLineTop(line);
             final int lineBottom = mLayout.getLineBottom(line);
 
             final Rect bounds = sCursorControllerTempRect;
             bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX) +
                     TextView.this.mScrollX;
-            bounds.top = (bottom ? lineBottom : lineTop - mHeight) + TextView.this.mScrollY;
+            bounds.top = lineBottom + TextView.this.mScrollY;
 
             bounds.right = bounds.left + width;
             bounds.bottom = bounds.top + height;
@@ -9107,10 +9123,10 @@
 
         public void updatePosition(HandleView handle, int x, int y) {
             final int previousOffset = getSelectionStart();
-            int offset = getHysteresisOffset(x, y, previousOffset);
+            final int newOffset = getOffset(x, y);
 
-            if (offset != previousOffset) {
-                updateOffset(handle, offset);
+            if (newOffset != previousOffset) {
+                updateOffset(handle, newOffset);
                 removePastePopupCallback();
             }
             hideDelayed();
@@ -9131,7 +9147,7 @@
                 return;
             }
 
-            getHandle().positionAtCursor(offset, true);
+            getHandle().positionAtCursor(offset);
         }
 
         public int getCurrentOffset(HandleView handle) {
@@ -9215,8 +9231,7 @@
             int selectionStart = getSelectionStart();
             int selectionEnd = getSelectionEnd();
 
-            final int previousOffset = handle == mStartHandle ? selectionStart : selectionEnd;
-            int offset = getHysteresisOffset(x, y, previousOffset);
+            int offset = getOffset(x, y);
 
             // Handle the case where start and end are swapped, making sure start <= end
             if (handle == mStartHandle) {
@@ -9275,8 +9290,8 @@
             }
 
             // The handles have been created since the controller isShowing().
-            mStartHandle.positionAtCursor(selectionStart, true);
-            mEndHandle.positionAtCursor(selectionEnd, true);
+            mStartHandle.positionAtCursor(selectionStart);
+            mEndHandle.positionAtCursor(selectionEnd);
         }
 
         public int getCurrentOffset(HandleView handle) {
@@ -9406,26 +9421,6 @@
         return offset;
     }
 
-    int getHysteresisOffset(int x, int y, int previousOffset) {
-        final Layout layout = getLayout();
-        if (layout == null) return -1;
-
-        int line = getLineAtCoordinate(y);
-        final int previousLine = layout.getLineForOffset(previousOffset);
-        final int previousLineTop = layout.getLineTop(previousLine);
-        final int previousLineBottom = layout.getLineBottom(previousLine);
-        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 8;
-
-        // If new line is just before or after previous line and y position is less than
-        // hysteresisThreshold away from previous line, keep cursor on previous line.
-        if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) ||
-            ((line == previousLine - 1) && ((previousLineTop - y)    < hysteresisThreshold))) {
-            line = previousLine;
-        }
-
-        return getOffsetAtCoordinate(line, x);
-    }
-
     private int convertToLocalHorizontalCoordinate(int x) {
         x -= getTotalPaddingLeft();
         // Clamp the position to inside of the view.