Ensure cursor drag is suppressed during selection drag

Once a selection drag starts, the cursor drag logic should be
skipped (short-circuited) until the selection drag ends.

Bug: 143852764, 146811252
Test: Manual and unit tests
  atest FrameworksCoreTests:EditorCursorDragTest

Change-Id: I8da82d5c197d8dcaa8d407ae11fac9a5b8521949
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 7e26f3a..f264f18 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1461,7 +1461,11 @@
         return false;
     }
 
-    void onTouchEvent(MotionEvent event) {
+    /**
+     * Handles touch events on an editable text view, implementing cursor movement, selection, etc.
+     */
+    @VisibleForTesting
+    public void onTouchEvent(MotionEvent event) {
         final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
         mLastButtonState = event.getButtonState();
         if (filterOutEvent) {
@@ -2423,7 +2427,9 @@
         return mSelectionControllerEnabled;
     }
 
-    private InsertionPointCursorController getInsertionController() {
+    /** Returns the controller for the insertion cursor. */
+    @VisibleForTesting
+    public @Nullable InsertionPointCursorController getInsertionController() {
         if (!mInsertionControllerEnabled) {
             return null;
         }
@@ -2438,8 +2444,9 @@
         return mInsertionPointCursorController;
     }
 
-    @Nullable
-    SelectionModifierCursorController getSelectionController() {
+    /** Returns the controller for selection. */
+    @VisibleForTesting
+    public @Nullable SelectionModifierCursorController getSelectionController() {
         if (!mSelectionControllerEnabled) {
             return null;
         }
@@ -5722,11 +5729,16 @@
         }
     }
 
-    class InsertionPointCursorController implements CursorController {
+    /** Controller for the insertion cursor. */
+    @VisibleForTesting
+    public class InsertionPointCursorController implements CursorController {
         private InsertionHandleView mHandle;
         private boolean mIsDraggingCursor;
 
         public void onTouchEvent(MotionEvent event) {
+            if (getSelectionController().isCursorBeingModified()) {
+                return;
+            }
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
                     mIsDraggingCursor = false;
@@ -5899,7 +5911,9 @@
         }
     }
 
-    class SelectionModifierCursorController implements CursorController {
+    /** Controller for selection. */
+    @VisibleForTesting
+    public class SelectionModifierCursorController implements CursorController {
         // The cursor controller handles, lazily created when shown.
         private SelectionHandleView mStartHandle;
         private SelectionHandleView mEndHandle;
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index 25ac400..f497db2 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -27,9 +27,12 @@
 
 import static org.hamcrest.Matchers.emptyString;
 import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.view.MotionEvent;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -221,4 +224,101 @@
         onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd")));
         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
     }
+
+    @Test
+    public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
+        String text = "testEditor_onTouchEvent_cursorDrag";
+        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. This should trigger a cursor drag.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
+        mInstrumentation.waitForIdleSync();
+        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();
+        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();
+        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();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
+    public void testEditor_onTouchEvent_selectionDrag() throws Throwable {
+        String text = "testEditor_onTouchEvent_selectionDrag";
+        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 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();
+        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();
+        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();
+        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();
+        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();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
+        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
+    }
+
+    private static MotionEvent upEvent(long downTime, long eventTime, float x, float y) {
+        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+    }
+
+    private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
+        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
+    }
 }