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

Commit 1f78b11b authored by Nikita Dubrovsky's avatar Nikita Dubrovsky
Browse files

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
parent 7304df0c
Loading
Loading
Loading
Loading
+3 −24
Original line number Diff line number Diff line
@@ -5921,8 +5921,6 @@ public class Editor {
        // 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 @@ public class Editor {
                                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 @@ public class Editor {
                            }
                            mDiscardNextActionUp = true;
                        }
                        mGestureStayedInTapRegion = true;
                        mHaventMovedEnoughToStartDrag = true;
                    }
                    break;
@@ -6059,25 +6055,8 @@ public class Editor {
                    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;
                        }
                        mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag();
                    }

                    if (isMouse && !isDragAcceleratorActive()) {
+13 −1
Original line number Diff line number Diff line
@@ -31,13 +31,15 @@ import java.lang.annotation.Retention;
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 @@ public class EditorTouchState {
        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 @@ public class EditorTouchState {
            }
            mLastDownX = event.getX();
            mLastDownY = event.getY();
            mLastDownMillis = event.getEventTime();
            mMovedEnoughForDrag = false;
            mIsDragCloseToVertical = false;
        } else if (action == MotionEvent.ACTION_UP) {
+66 −20
Original line number Diff line number Diff line
@@ -225,6 +225,61 @@ public class EditorCursorDragTest {
        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
    }

    @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";
@@ -237,29 +292,25 @@ public class EditorCursorDragTest {
        // 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 @@ public class EditorCursorDragTest {
        // 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());
    }
+54 −0
Original line number Diff line number Diff line
@@ -119,6 +119,60 @@ public class EditorTouchStateTest {
                MultiTapStatus.DOUBLE_TAP, false);
    }

    @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.