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;