Simplify state in SelectionModifierCursorController

This change removes some code from SelectionModifierCursorController
in favor of calling EditorTouchState instead.

Bug: 143852764
Test: Manual and unit tests
  atest FrameworksCoreTests:EditorTouchStateTest
  atest FrameworksCoreTests:EditorCursorDragTest
  atest FrameworksCoreTests:TextViewActivityTest
  atest FrameworksCoreTests:SuggestionsPopupWindowTest

Change-Id: I40f0ad8bd4eb0d4af33d0e2cd1a79c99812a9c40
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a0cf534..20af76b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -5921,8 +5921,6 @@
         // The offsets of that last touch down event. Remembered to start selection there.
         private int mMinTouchOffset, mMaxTouchOffset;
 
-        private boolean mGestureStayedInTapRegion;
-
         // Where the user first starts the drag motion.
         private int mStartOffset = -1;
 
@@ -6029,8 +6027,7 @@
                                 eventX, eventY);
 
                         // Double tap detection
-                        if (mGestureStayedInTapRegion
-                                && mTouchState.isMultiTapInSameArea()
+                        if (mTouchState.isMultiTapInSameArea()
                                 && (isMouse || isPositionOnText(eventX, eventY))) {
                             if (TextView.DEBUG_CURSOR) {
                                 logCursor("SelectionModifierCursorController: onTouchEvent",
@@ -6043,7 +6040,6 @@
                             }
                             mDiscardNextActionUp = true;
                         }
-                        mGestureStayedInTapRegion = true;
                         mHaventMovedEnoughToStartDrag = true;
                     }
                     break;
@@ -6059,25 +6055,8 @@
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    final ViewConfiguration viewConfig = ViewConfiguration.get(
-                            mTextView.getContext());
-
-                    if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
-                        final float deltaX = eventX - mTouchState.getLastDownX();
-                        final float deltaY = eventY - mTouchState.getLastDownY();
-                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
-
-                        if (mGestureStayedInTapRegion) {
-                            int doubleTapTouchSlop = viewConfig.getScaledDoubleTapTouchSlop();
-                            mGestureStayedInTapRegion =
-                                    distanceSquared <= doubleTapTouchSlop * doubleTapTouchSlop;
-                        }
-                        if (mHaventMovedEnoughToStartDrag) {
-                            // We don't start dragging until the user has moved enough.
-                            int touchSlop = viewConfig.getScaledTouchSlop();
-                            mHaventMovedEnoughToStartDrag =
-                                    distanceSquared <= touchSlop * touchSlop;
-                        }
+                    if (mHaventMovedEnoughToStartDrag) {
+                        mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag();
                     }
 
                     if (isMouse && !isDragAcceleratorActive()) {
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index d53099d..6277afe 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -31,13 +31,15 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Helper class used by {@link Editor} to track state for touch events.
+ * Helper class used by {@link Editor} to track state for touch events. Ideally the logic here
+ * should be replaced with {@link android.view.GestureDetector}.
  *
  * @hide
  */
 @VisibleForTesting(visibility = PACKAGE)
 public class EditorTouchState {
     private float mLastDownX, mLastDownY;
+    private long mLastDownMillis;
     private float mLastUpX, mLastUpY;
     private long mLastUpMillis;
 
@@ -106,9 +108,18 @@
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+
+            // We check both the time between the last up and current down event, as well as the
+            // time between the first down and up events. The latter check is necessary to handle
+            // the case when the user taps, drags/holds for some time, and then lifts up and
+            // quickly taps in the same area. This scenario should not be treated as a double-tap.
+            // This follows the behavior in GestureDetector.
             final long millisSinceLastUp = event.getEventTime() - mLastUpMillis;
+            final long millisBetweenLastDownAndLastUp = mLastUpMillis - mLastDownMillis;
+
             // Detect double tap and triple click.
             if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout()
+                    && millisBetweenLastDownAndLastUp <= ViewConfiguration.getDoubleTapTimeout()
                     && (mMultiTapStatus == MultiTapStatus.FIRST_TAP
                     || (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) {
                 if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) {
@@ -133,6 +144,7 @@
             }
             mLastDownX = event.getX();
             mLastDownY = event.getY();
+            mLastDownMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
             mIsDragCloseToVertical = false;
         } else if (action == MotionEvent.ACTION_UP) {
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index f497db2..89c2374 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -226,6 +226,61 @@
     }
 
     @Test
+    public void testEditor_onTouchEvent_quickTapAfterDrag() throws Throwable {
+        String text = "Hi world!";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a tap-and-drag gesture.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event4Time = 2004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate a quick tap after the drag, near the location where the drag ended.
+        long event5Time = 2005;
+        MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event6Time = 2006;
+        MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate another quick tap in the same location; now selection should be triggered.
+        long event7Time = 2007;
+        MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
     public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
         String text = "testEditor_onTouchEvent_cursorDrag";
         onView(withId(R.id.textview)).perform(replaceText(text));
@@ -237,29 +292,25 @@
         // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
-        MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertTrue(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
-        MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
@@ -276,36 +327,31 @@
         // Simulate a double-tap followed by a drag. This should trigger a selection drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
         MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
         MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event5Time = 1005;
         MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event5));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 6adb1b8..215d0b8 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -120,6 +120,60 @@
     }
 
     @Test
+    public void testUpdate_doubleTap_delayAfterFirstDownEvent() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
+        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event3Time = event2Time + 1;
+        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+    }
+
+    @Test
+    public void testUpdate_quickTapAfterDrag() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_MOVE event.
+        long event2Time = 1001;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event3Time = 5000;
+        MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event4Time = event3Time + 1;
+        MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
+    }
+
+    @Test
     public void testUpdate_tripleClick_mouse() throws Exception {
         // Simulate an ACTION_DOWN event.
         long event1Time = 1000;