Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21c6a965 authored by Nikita Dubrovsky's avatar Nikita Dubrovsky
Browse files

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
parent cd36c5ea
Loading
Loading
Loading
Loading
+20 −6
Original line number Diff line number Diff line
@@ -1461,7 +1461,11 @@ public class Editor {
        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 @@ public class Editor {
        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 @@ public class Editor {
        return mInsertionPointCursorController;
    }

    @Nullable
    SelectionModifierCursorController getSelectionController() {
    /** Returns the controller for selection. */
    @VisibleForTesting
    public @Nullable SelectionModifierCursorController getSelectionController() {
        if (!mSelectionControllerEnabled) {
            return null;
        }
@@ -5722,11 +5729,16 @@ public class Editor {
        }
    }

    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 @@ public class Editor {
        }
    }

    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;
+100 −0
Original line number Diff line number Diff line
@@ -27,9 +27,12 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;

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 @@ public class EditorCursorDragTest {
        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);
    }
}