Merge "Prevents multi touch among TextView and handle reviews." into rvc-dev am: 9b6af1dcc5

Change-Id: I8add9f69ccb0d582bbc6be0582ddb04696a8a8f6
This commit is contained in:
Shu Chen
2020-04-16 15:43:54 +00:00
committed by Automerger Merge Worker
3 changed files with 111 additions and 0 deletions

View File

@@ -5440,6 +5440,9 @@ public class Editor {
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!mTextView.isFromPrimePointer(ev, true)) {
return true;
}
if (mFlagInsertionHandleGesturesEnabled && mFlagCursorDragFromAnywhereEnabled) {
// Should only enable touch through when cursor drag is enabled.
// Otherwise the insertion handle view cannot be moved.
@@ -5908,6 +5911,9 @@ public class Editor {
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mTextView.isFromPrimePointer(event, true)) {
return true;
}
boolean superResult = super.onTouchEvent(event);
switch (event.getActionMasked()) {

View File

@@ -855,6 +855,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int mTextEditSuggestionContainerLayout;
int mTextEditSuggestionHighlightStyle;
private static final int NO_POINTER_ID = -1;
/**
* The prime (the 1st finger) pointer id which is used as a lock to prevent multi touch among
* TextView and the handle views which are rendered on popup windows.
*/
private int mPrimePointerId = NO_POINTER_ID;
/**
* Whether the prime pointer is from the event delivered to selection handle or insertion
* handle.
*/
private boolean mIsPrimePointerFromHandleView;
/**
* {@link EditText} specific data, created on demand when one of the Editor fields is used.
* See {@link #createEditorIfNeeded()}.
@@ -10886,6 +10899,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
/**
* Called from onTouchEvent() to prevent the touches by secondary fingers.
* Dragging on handles can revise cursor/selection, so can dragging on the text view.
* This method is a lock to avoid processing multiple fingers on both text view and handles.
* Note: multiple fingers on handles (e.g. 2 fingers on the 2 selection handles) should work.
*
* @param event The motion event that is being handled and carries the pointer info.
* @param fromHandleView true if the event is delivered to selection handle or insertion
* handle; false if this event is delivered to TextView.
* @return Returns true to indicate that onTouchEvent() can continue processing the motion
* event, otherwise false.
* - Always returns true for the first finger.
* - For secondary fingers, if the first or current finger is from TextView, returns false.
* This is to make touch mutually exclusive between the TextView and the handles, but
* not among the handles.
*/
boolean isFromPrimePointer(MotionEvent event, boolean fromHandleView) {
if (mPrimePointerId == NO_POINTER_ID) {
mPrimePointerId = event.getPointerId(0);
mIsPrimePointerFromHandleView = fromHandleView;
} else if (mPrimePointerId != event.getPointerId(0)) {
return mIsPrimePointerFromHandleView && fromHandleView;
}
if (event.getActionMasked() == MotionEvent.ACTION_UP
|| event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
mPrimePointerId = -1;
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (DEBUG_CURSOR) {
@@ -10894,6 +10937,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
MotionEvent.actionToString(event.getActionMasked()),
event.getX(), event.getY());
}
if (!isFromPrimePointer(event, false)) {
return true;
}
final int action = event.getActionMasked();
if (mEditor != null) {

View File

@@ -527,6 +527,47 @@ public class EditorCursorDragTest {
.isEqualTo(2);
}
@Test
public void testCursorDrag_multiTouch() throws Throwable {
String text = "line1: This is the 1st line: A";
onView(withId(R.id.textview)).perform(replaceText(text));
TextView tv = mActivity.findViewById(R.id.textview);
Editor editor = tv.getEditorForTesting();
final int startIndex = text.indexOf("1st line");
Layout layout = tv.getLayout();
final float cursorStartX =
layout.getPrimaryHorizontal(startIndex) + tv.getTotalPaddingLeft();
final float cursorStartY = layout.getLineTop(1) + tv.getTotalPaddingTop();
// Taps to show the insertion handle.
tapAtPoint(tv, cursorStartX, cursorStartY);
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(startIndex));
View handleView = editor.getInsertionController().getHandle();
// Taps & holds the insertion handle.
long handleDownTime = sTicker.addAndGet(10_000);
long eventTime = handleDownTime;
dispatchTouchEvent(handleView, downEvent(handleView, handleDownTime, eventTime++, 0, 0));
// Tries to Drag the cursor, with the pointer id > 0 (meaning the 2nd finger).
long cursorDownTime = eventTime++;
dispatchTouchEvent(tv, obtainTouchEventWithPointerId(
tv, MotionEvent.ACTION_DOWN, cursorDownTime, eventTime++, 1,
cursorStartX - 50, cursorStartY));
dispatchTouchEvent(tv, obtainTouchEventWithPointerId(
tv, MotionEvent.ACTION_MOVE, cursorDownTime, eventTime++, 1,
cursorStartX - 100, cursorStartY));
dispatchTouchEvent(tv, obtainTouchEventWithPointerId(
tv, MotionEvent.ACTION_UP, cursorDownTime, eventTime++, 1,
cursorStartX - 100, cursorStartY));
// Checks the cursor drag doesn't work while the handle is being hold.
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(startIndex));
// Finger up on the insertion handle.
dispatchTouchEvent(handleView, upEvent(handleView, handleDownTime, eventTime, 0, 0));
}
@Test
public void testCursorDrag_snapDistance() throws Throwable {
String text = "line1: This is the 1st line: A\n"
@@ -626,6 +667,24 @@ public class EditorCursorDragTest {
return event;
}
private MotionEvent obtainTouchEventWithPointerId(
View view, int action, long downTime, long eventTime, int pointerId, float x, float y) {
Rect r = new Rect();
view.getBoundsOnScreen(r);
float rawX = x + r.left;
float rawY = y + r.top;
MotionEvent.PointerCoords coordinates = new MotionEvent.PointerCoords();
coordinates.x = rawX;
coordinates.y = rawY;
MotionEvent event = MotionEvent.obtain(
downTime, eventTime, action, 1, new int[] {pointerId},
new MotionEvent.PointerCoords[] {coordinates},
0, 1f, 1f, 0, 0, 0, 0);
view.toLocalMotionEvent(event);
mMotionEvents.add(event);
return event;
}
private MotionEvent obtainMouseEvent(
View view, int action, long downTime, long eventTime, float x, float y) {
MotionEvent event = obtainTouchEvent(view, action, downTime, eventTime, x, y);