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

Commit eb8b1ba3 authored by Shu Chen's avatar Shu Chen
Browse files

Prevents multi touch among TextView and handle reviews.

 - First touch on TextView blocks secondary touches on handles.
 - First touch on handles blocks later touches on TextView but doesn't block secondary touches on other handles.

Bug: 150995597
Test: manually tested & automation tests:
  atest FrameworksCoreTests:EditorCursorDragTest
  atest FrameworksCoreTests:TextViewActivityTest
Change-Id: I7717fc061fc81514fc1dad0d3acbc73e683516cf
parent 449baa2e
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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()) {
+46 −0
Original line number Diff line number Diff line
@@ -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) {
+59 −0
Original line number Diff line number Diff line
@@ -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);