Merge "Simplify state in SelectionModifierCursorController"

This commit is contained in:
Nikita Dubrovsky
2020-01-10 15:55:50 +00:00
committed by Android (Google) Code Review
4 changed files with 136 additions and 45 deletions

View File

@@ -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;
}
if (mHaventMovedEnoughToStartDrag) {
mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag();
}
if (isMouse && !isDragAcceleratorActive()) {

View File

@@ -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) {

View File

@@ -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());
}

View File

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