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

Commit b7e8cfc3 authored by Nikita Dubrovsky's avatar Nikita Dubrovsky
Browse files

Handle ACTION_CANCEL in EditorTouchState (editable TextView)

When testDragAndDrop_longClick() performs the long-press, the events
triggered are ACTION_DOWN, ACTION_CANCEL, ACTION_DOWN, ACTION_CANCEL.
It's not clear why there is the additional ACTION_DOWN / ACTION_CANCEL
pair; performing the same gesture manually only produces a single
ACTION_DOWN / ACTION_CANCEL pair. Still, Editor should be able to handle
this scenario since ACTION_CANCEL is a valid event.

Previously there was no handling for ACTION_CANCEL in EditorTouchState
at all, so the sequence of events triggered by the test would end up
getting treated as a double-tap scenario and drag-and-drop would not be
started.

This CL adds handling for ACTION_CANCEL in EditorTouchState, attempting
to follow GestureDetector as closely as possible (essentially resetting
state whenever ACTION_CANCEL is received, so that subsequent events
start a "fresh sequence").

Bug: 29591177
Test: Manual and ran tests
  atest FrameworksCoreTests:TextViewActivityMouseTest
  atest FrameworksCoreTests:TextViewActivityTest
  atest FrameworksCoreTests:EditorTouchStateTest
  atest FrameworksCoreTests:EditorCursorDragTest

Change-Id: Ia0ee531af64083a4f670842188bdc7e35b617397
parent 4dca994b
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -173,6 +173,13 @@ public class EditorTouchState {
                    mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared;
                }
            }
        } else if (action == MotionEvent.ACTION_CANCEL) {
            mLastDownMillis = 0;
            mLastUpMillis = 0;
            mMultiTapStatus = MultiTapStatus.NONE;
            mMultiTapInSameArea = false;
            mMovedEnoughForDrag = false;
            mIsDragCloseToVertical = false;
        }
    }

+171 −30
Original line number Diff line number Diff line
@@ -47,25 +47,33 @@ public class EditorTouchStateTest {
        mConfig = new ViewConfiguration();
    }

    @Test
    public void testIsDistanceWithin() throws Exception {
        assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
        assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
        assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
        assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
    }

    @Test
    public void testUpdate_singleTap() 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);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate an ACTION_UP event.
        long event2Time = 1001;
        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
        mTouchState.update(event2, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);

        // Generate an ACTION_DOWN event whose time is after the double-tap timeout.
        long event3Time = event2Time + ViewConfiguration.getDoubleTapTimeout() + 1;
        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
        mTouchState.update(event3, mConfig);
        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
    }

    @Test
@@ -74,13 +82,13 @@ public class EditorTouchStateTest {
        long event1Time = 1000;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate an ACTION_UP event.
        long event2Time = 1001;
        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
        mTouchState.update(event2, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);

        // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
        long event3Time = 1002;
@@ -96,13 +104,13 @@ public class EditorTouchStateTest {
        long event1Time = 1000;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate an ACTION_UP event.
        long event2Time = 1001;
        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
        mTouchState.update(event2, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);

        // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
        long event3Time = 1002;
@@ -125,13 +133,13 @@ public class EditorTouchStateTest {
        long event1Time = 1000;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // 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);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);

        // 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
@@ -140,7 +148,7 @@ public class EditorTouchStateTest {
        long event3Time = event2Time + 1;
        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
        mTouchState.update(event3, mConfig);
        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
    }

    @Test
@@ -149,19 +157,19 @@ public class EditorTouchStateTest {
        long event1Time = 1000;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // 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);
        assertDrag(mTouchState, 20f, 30f, 0, 0, false);

        // 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);
        assertSingleTap(mTouchState, 20f, 30f, 200f, 31f);

        // 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
@@ -170,7 +178,7 @@ public class EditorTouchStateTest {
        long event4Time = event3Time + 1;
        MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
        mTouchState.update(event4, mConfig);
        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f);
    }

    @Test
@@ -180,14 +188,14 @@ public class EditorTouchStateTest {
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        event1.setSource(InputDevice.SOURCE_MOUSE);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate an ACTION_UP event.
        long event2Time = 1001;
        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
        event2.setSource(InputDevice.SOURCE_MOUSE);
        mTouchState.update(event2, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);

        // Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
        long event3Time = 1002;
@@ -220,13 +228,13 @@ public class EditorTouchStateTest {
        long event1Time = 1000;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate an ACTION_UP event.
        long event2Time = 1001;
        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
        mTouchState.update(event2, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);

        // Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
        long event3Time = 1002;
@@ -246,7 +254,7 @@ public class EditorTouchStateTest {
        long event5Time = 1004;
        MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
        mTouchState.update(event5, mConfig);
        assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false);
        assertSingleTap(mTouchState, 22f, 32f, 21f, 31f);
    }

    @Test
@@ -255,13 +263,13 @@ public class EditorTouchStateTest {
        long event1Time = 1000;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate an ACTION_MOVE event whose location is not far enough to start a drag.
        long event2Time = 1001;
        MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
        mTouchState.update(event2, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
        int touchSlop = mConfig.getScaledTouchSlop();
@@ -270,21 +278,135 @@ public class EditorTouchStateTest {
        long event3Time = 1002;
        MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
        mTouchState.update(event3, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
        assertDrag(mTouchState, 20f, 30f, 0, 0, false);

        // Simulate an ACTION_UP event.
        long event4Time = 1003;
        MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f);
        mTouchState.update(event4, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 200f, 300f, false);
        assertSingleTap(mTouchState, 20f, 30f, 200f, 300f);
    }

    @Test
    public void testIsDistanceWithin() throws Exception {
        assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
        assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
        assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
        assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
    public void testUpdate_drag_startsCloseToVerticalThenHorizontal() throws Exception {
        // Simulate an ACTION_DOWN event.
        long event1Time = 1001;
        MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 0f, 0f, 0, 0);

        // Simulate an ACTION_MOVE event that is < 30 deg from vertical.
        long event2Time = 1002;
        MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
        mTouchState.update(event2, mConfig);
        assertDrag(mTouchState, 0f, 0f, 0, 0, true);

        // Simulate another ACTION_MOVE event that is horizontal from the original down event.
        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
        // initial direction of movement.
        long event3Time = 1003;
        MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
        mTouchState.update(event3, mConfig);
        assertDrag(mTouchState, 0f, 0f, 0, 0, true);

        // Simulate an ACTION_UP event.
        long event4Time = 1004;
        MotionEvent event4 = upEvent(event1Time, event4Time, 200f, 0f);
        mTouchState.update(event4, mConfig);
        assertSingleTap(mTouchState, 0f, 0f, 200f, 0f);
    }

    @Test
    public void testUpdate_drag_startsHorizontalThenVertical() throws Exception {
        // Simulate an ACTION_DOWN event.
        long event1Time = 1001;
        MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 0f, 0f, 0, 0);

        // Simulate an ACTION_MOVE event that is > 30 deg from vertical.
        long event2Time = 1002;
        MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 173f);
        mTouchState.update(event2, mConfig);
        assertDrag(mTouchState, 0f, 0f, 0, 0, false);

        // Simulate another ACTION_MOVE event that is vertical from the original down event.
        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
        // initial direction of movement.
        long event3Time = 1003;
        MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
        mTouchState.update(event3, mConfig);
        assertDrag(mTouchState, 0f, 0f, 0, 0, false);

        // Simulate an ACTION_UP event.
        long event4Time = 1004;
        MotionEvent event4 = upEvent(event1Time, event4Time, 0f, 200f);
        mTouchState.update(event4, mConfig);
        assertSingleTap(mTouchState, 0f, 0f, 0f, 200f);
    }

    @Test
    public void testUpdate_cancelAfterDown() throws Exception {
        // Simulate an ACTION_DOWN event.
        long event1Time = 1001;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate an ACTION_CANCEL event.
        long event2Time = 1002;
        MotionEvent event2 = cancelEvent(event1Time, event2Time, 20f, 30f);
        mTouchState.update(event2, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
    }

    @Test
    public void testUpdate_cancelAfterDrag() throws Exception {
        // Simulate an ACTION_DOWN event.
        long event1Time = 1001;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
        long event2Time = 1002;
        MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
        mTouchState.update(event2, mConfig);
        assertDrag(mTouchState, 20f, 30f, 0, 0, false);

        // Simulate an ACTION_CANCEL event.
        long event3Time = 1003;
        MotionEvent event3 = cancelEvent(event1Time, event3Time, 200f, 30f);
        mTouchState.update(event3, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);
    }

    @Test
    public void testUpdate_cancelAfterMultitap() throws Exception {
        // Simulate an ACTION_DOWN event.
        long event1Time = 1001;
        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
        mTouchState.update(event1, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 0, 0);

        // Simulate an ACTION_UP event.
        long event2Time = 1002;
        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
        mTouchState.update(event2, mConfig);
        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);

        // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
        long event3Time = 1003;
        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
        mTouchState.update(event3, mConfig);
        assertMultiTap(mTouchState, 22f, 33f, 20f, 30f,
                MultiTapStatus.DOUBLE_TAP, true);

        // Simulate an ACTION_CANCEL event.
        long event4Time = 1004;
        MotionEvent event4 = cancelEvent(event3Time, event4Time, 20f, 30f);
        mTouchState.update(event4, mConfig);
        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
    }

    private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
@@ -299,8 +421,25 @@ public class EditorTouchStateTest {
        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
    }

    private static MotionEvent cancelEvent(long downTime, long eventTime, float x, float y) {
        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, x, y, 0);
    }

    private static void assertSingleTap(EditorTouchState touchState, float lastDownX,
            float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) {
            float lastDownY, float lastUpX, float lastUpY) {
        assertThat(touchState.getLastDownX(), is(lastDownX));
        assertThat(touchState.getLastDownY(), is(lastDownY));
        assertThat(touchState.getLastUpX(), is(lastUpX));
        assertThat(touchState.getLastUpY(), is(lastUpY));
        assertThat(touchState.isDoubleTap(), is(false));
        assertThat(touchState.isTripleClick(), is(false));
        assertThat(touchState.isMultiTap(), is(false));
        assertThat(touchState.isMultiTapInSameArea(), is(false));
        assertThat(touchState.isMovedEnoughForDrag(), is(false));
    }

    private static void assertDrag(EditorTouchState touchState, float lastDownX,
            float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
        assertThat(touchState.getLastDownX(), is(lastDownX));
        assertThat(touchState.getLastDownY(), is(lastDownY));
        assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -309,7 +448,8 @@ public class EditorTouchStateTest {
        assertThat(touchState.isTripleClick(), is(false));
        assertThat(touchState.isMultiTap(), is(false));
        assertThat(touchState.isMultiTapInSameArea(), is(false));
        assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag));
        assertThat(touchState.isMovedEnoughForDrag(), is(true));
        assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
    }

    private static void assertMultiTap(EditorTouchState touchState,
@@ -325,5 +465,6 @@ public class EditorTouchStateTest {
                || multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
        assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
        assertThat(touchState.isMovedEnoughForDrag(), is(false));
        assertThat(touchState.isDragCloseToVertical(), is(false));
    }
}
+0 −2
Original line number Diff line number Diff line
@@ -47,7 +47,6 @@ import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;

import androidx.test.filters.MediumTest;
import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

@@ -193,7 +192,6 @@ public class TextViewActivityMouseTest {
    }

    @Test
    @Suppress // Consistently failing. b/29591177
    public void testDragAndDrop_longClick() {
        final String text = "abc def ghi.";
        onView(withId(R.id.textview)).perform(mouseClick());