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

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

Consolidate tracking of touch state in Editor (editable TextView)

The goal of these changes is to consolidate the state that needs to be
tracked for user motion events (touches, clicks, etc), in order to make
it easier to add/update functionality in the future.

These changes are intended to fully preserve existing behavior from the
user perspective. For the most part, the logic is completely unchanged
(just that the code is pulled out), but it's worth calling out the
following subtle changes to the logic. These changes don't affect any
user-visible behavior.

1) In Editor.onTouchEvent, the recorded position of the last down event
is now updated before SelectionModifierCursorController.onTouchEvent()
is invoked.

2) In Editor.updateTapState (now in EditorTouchState.update), instead
of using SystemClock.uptimeMillis() for the event time, we use
event.getEventTime().

Bug: 143852764
Test: Manual testing and unit test
  atest FrameworksCoreTests:EditorTouchStateTest
Change-Id: Ic40b5e53412294d365bc9d787e7013a44fe11ffa
parent fe903fb8
Loading
Loading
Loading
Loading
+78 −141
Original line number Original line Diff line number Diff line
@@ -152,6 +152,9 @@ public class Editor {
    // handles.
    // handles.
    private static final boolean FLAG_USE_MAGNIFIER = true;
    private static final boolean FLAG_USE_MAGNIFIER = true;


    private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
    private static final int RECENT_CUT_COPY_DURATION_MS = 15 * 1000; // 15 seconds in millis

    static final int BLINK = 500;
    static final int BLINK = 500;
    private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
    private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
    private static final float LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS = 0.5f;
    private static final float LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS = 0.5f;
@@ -326,8 +329,6 @@ public class Editor {
    // Global listener that detects changes in the global position of the TextView
    // Global listener that detects changes in the global position of the TextView
    private PositionListener mPositionListener;
    private PositionListener mPositionListener;


    private float mLastDownPositionX, mLastDownPositionY;
    private float mLastUpPositionX, mLastUpPositionY;
    private float mContextMenuAnchorX, mContextMenuAnchorY;
    private float mContextMenuAnchorX, mContextMenuAnchorY;
    Callback mCustomSelectionActionModeCallback;
    Callback mCustomSelectionActionModeCallback;
    Callback mCustomInsertionActionModeCallback;
    Callback mCustomInsertionActionModeCallback;
@@ -336,18 +337,11 @@ public class Editor {
    @UnsupportedAppUsage
    @UnsupportedAppUsage
    boolean mCreatedWithASelection;
    boolean mCreatedWithASelection;


    // Indicates the current tap state (first tap, double tap, or triple click).
    private int mTapState = TAP_STATE_INITIAL;
    private long mLastTouchUpTime = 0;
    private static final int TAP_STATE_INITIAL = 0;
    private static final int TAP_STATE_FIRST_TAP = 1;
    private static final int TAP_STATE_DOUBLE_TAP = 2;
    // Only for mouse input.
    private static final int TAP_STATE_TRIPLE_CLICK = 3;

    // The button state as of the last time #onTouchEvent is called.
    // The button state as of the last time #onTouchEvent is called.
    private int mLastButtonState;
    private int mLastButtonState;


    private final EditorTouchState mTouchState = new EditorTouchState();

    private Runnable mInsertionActionModeRunnable;
    private Runnable mInsertionActionModeRunnable;


    // The span controller helps monitoring the changes to which the Editor needs to react:
    // The span controller helps monitoring the changes to which the Editor needs to react:
@@ -1193,10 +1187,10 @@ public class Editor {
            logCursor("performLongClick", "handled=%s", handled);
            logCursor("performLongClick", "handled=%s", handled);
        }
        }
        // Long press in empty space moves cursor and starts the insertion action mode.
        // Long press in empty space moves cursor and starts the insertion action mode.
        if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY)
        if (!handled && !isPositionOnText(mTouchState.getLastDownX(), mTouchState.getLastDownY())
                && mInsertionControllerEnabled) {
                && mInsertionControllerEnabled) {
            final int offset = mTextView.getOffsetForPosition(mLastDownPositionX,
            final int offset = mTextView.getOffsetForPosition(mTouchState.getLastDownX(),
                    mLastDownPositionY);
                    mTouchState.getLastDownY());
            Selection.setSelection((Spannable) mTextView.getText(), offset);
            Selection.setSelection((Spannable) mTextView.getText(), offset);
            getInsertionController().show();
            getInsertionController().show();
            mIsInsertionActionModeStartPending = true;
            mIsInsertionActionModeStartPending = true;
@@ -1240,11 +1234,11 @@ public class Editor {
    }
    }


    float getLastUpPositionX() {
    float getLastUpPositionX() {
        return mLastUpPositionX;
        return mTouchState.getLastUpX();
    }
    }


    float getLastUpPositionY() {
    float getLastUpPositionY() {
        return mLastUpPositionY;
        return mTouchState.getLastUpY();
    }
    }


    private long getLastTouchOffsets() {
    private long getLastTouchOffsets() {
@@ -1279,6 +1273,9 @@ public class Editor {
                // Has to be done before onTakeFocus, which can be overloaded.
                // Has to be done before onTakeFocus, which can be overloaded.
                final int lastTapPosition = getLastTapPosition();
                final int lastTapPosition = getLastTapPosition();
                if (lastTapPosition >= 0) {
                if (lastTapPosition >= 0) {
                    if (TextView.DEBUG_CURSOR) {
                        logCursor("onFocusChanged", "setting cursor position: %d", lastTapPosition);
                    }
                    Selection.setSelection((Spannable) mTextView.getText(), lastTapPosition);
                    Selection.setSelection((Spannable) mTextView.getText(), lastTapPosition);
                }
                }


@@ -1443,39 +1440,6 @@ public class Editor {
        }
        }
    }
    }


    private void updateTapState(MotionEvent event) {
        final int action = event.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
            // Detect double tap and triple click.
            if (((mTapState == TAP_STATE_FIRST_TAP)
                    || ((mTapState == TAP_STATE_DOUBLE_TAP) && isMouse))
                            && (SystemClock.uptimeMillis() - mLastTouchUpTime)
                                    <= ViewConfiguration.getDoubleTapTimeout()) {
                if (mTapState == TAP_STATE_FIRST_TAP) {
                    mTapState = TAP_STATE_DOUBLE_TAP;
                } else {
                    mTapState = TAP_STATE_TRIPLE_CLICK;
                }
                if (TextView.DEBUG_CURSOR) {
                    logCursor("updateTapState", "ACTION_DOWN: %s tap detected",
                            (mTapState == TAP_STATE_DOUBLE_TAP ? "double" : "triple"));
                }
            } else {
                mTapState = TAP_STATE_FIRST_TAP;
                if (TextView.DEBUG_CURSOR) {
                    logCursor("updateTapState", "ACTION_DOWN: first tap detected");
                }
            }
        }
        if (action == MotionEvent.ACTION_UP) {
            mLastTouchUpTime = SystemClock.uptimeMillis();
            if (TextView.DEBUG_CURSOR) {
                logCursor("updateTapState", "ACTION_UP");
            }
        }
    }

    private boolean shouldFilterOutTouchEvent(MotionEvent event) {
    private boolean shouldFilterOutTouchEvent(MotionEvent event) {
        if (!event.isFromSource(InputDevice.SOURCE_MOUSE)) {
        if (!event.isFromSource(InputDevice.SOURCE_MOUSE)) {
            return false;
            return false;
@@ -1503,7 +1467,8 @@ public class Editor {
            }
            }
            return;
            return;
        }
        }
        updateTapState(event);
        ViewConfiguration viewConfiguration = ViewConfiguration.get(mTextView.getContext());
        mTouchState.update(event, viewConfiguration);
        updateFloatingToolbarVisibility(event);
        updateFloatingToolbarVisibility(event);


        if (hasSelectionController()) {
        if (hasSelectionController()) {
@@ -1515,15 +1480,7 @@ public class Editor {
            mShowSuggestionRunnable = null;
            mShowSuggestionRunnable = null;
        }
        }


        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
            mLastUpPositionX = event.getX();
            mLastUpPositionY = event.getY();
        }

        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            mLastDownPositionX = event.getX();
            mLastDownPositionY = event.getY();

            // Reset this state; it will be re-set if super.onTouchEvent
            // Reset this state; it will be re-set if super.onTouchEvent
            // causes focus to move to the view.
            // causes focus to move to the view.
            mTouchFocusSelected = false;
            mTouchFocusSelected = false;
@@ -5067,7 +5024,10 @@ public class Editor {
        public boolean onTouchEvent(MotionEvent ev) {
        public boolean onTouchEvent(MotionEvent ev) {
            if (TextView.DEBUG_CURSOR) {
            if (TextView.DEBUG_CURSOR) {
                logCursor(this.getClass().getSimpleName() + ": HandleView: onTouchEvent",
                logCursor(this.getClass().getSimpleName() + ": HandleView: onTouchEvent",
                        MotionEvent.actionToString(ev.getActionMasked()));
                        "%d: %s (%f,%f)",
                        ev.getSequenceNumber(),
                        MotionEvent.actionToString(ev.getActionMasked()),
                        ev.getX(), ev.getY());
            }
            }


            updateFloatingToolbarVisibility(ev);
            updateFloatingToolbarVisibility(ev);
@@ -5145,56 +5105,14 @@ public class Editor {
    }
    }


    private class InsertionHandleView extends HandleView {
    private class InsertionHandleView extends HandleView {
        private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
        private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds

        // Used to detect taps on the insertion handle, which will affect the insertion action mode
        // Used to detect taps on the insertion handle, which will affect the insertion action mode
        private float mDownPositionX, mDownPositionY;
        private float mLastDownRawX, mLastDownRawY;
        private Runnable mHider;
        private Runnable mHider;


        public InsertionHandleView(Drawable drawable) {
        public InsertionHandleView(Drawable drawable) {
            super(drawable, drawable, com.android.internal.R.id.insertion_handle);
            super(drawable, drawable, com.android.internal.R.id.insertion_handle);
        }
        }


        @Override
        public void show() {
            super.show();

            final long durationSinceCutOrCopy =
                    SystemClock.uptimeMillis() - TextView.sLastCutCopyOrTextChangedTime;

            // Cancel the single tap delayed runnable.
            if (mInsertionActionModeRunnable != null
                    && ((mTapState == TAP_STATE_DOUBLE_TAP)
                            || (mTapState == TAP_STATE_TRIPLE_CLICK)
                            || isCursorInsideEasyCorrectionSpan())) {
                mTextView.removeCallbacks(mInsertionActionModeRunnable);
            }

            // Prepare and schedule the single tap runnable to run exactly after the double tap
            // timeout has passed.
            if ((mTapState != TAP_STATE_DOUBLE_TAP) && (mTapState != TAP_STATE_TRIPLE_CLICK)
                    && !isCursorInsideEasyCorrectionSpan()
                    && (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)) {
                if (mTextActionMode == null) {
                    if (mInsertionActionModeRunnable == null) {
                        mInsertionActionModeRunnable = new Runnable() {
                            @Override
                            public void run() {
                                startInsertionActionMode();
                            }
                        };
                    }
                    mTextView.postDelayed(
                            mInsertionActionModeRunnable,
                            ViewConfiguration.getDoubleTapTimeout() + 1);
                }

            }

            hideAfterDelay();
        }

        private void hideAfterDelay() {
        private void hideAfterDelay() {
            if (mHider == null) {
            if (mHider == null) {
                mHider = new Runnable() {
                mHider = new Runnable() {
@@ -5250,8 +5168,8 @@ public class Editor {


            switch (ev.getActionMasked()) {
            switch (ev.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_DOWN:
                    mDownPositionX = ev.getRawX();
                    mLastDownRawX = ev.getRawX();
                    mDownPositionY = ev.getRawY();
                    mLastDownRawY = ev.getRawY();
                    updateMagnifier(ev);
                    updateMagnifier(ev);
                    break;
                    break;


@@ -5261,8 +5179,8 @@ public class Editor {


                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_UP:
                    if (!offsetHasBeenChanged()) {
                    if (!offsetHasBeenChanged()) {
                        final float deltaX = mDownPositionX - ev.getRawX();
                        final float deltaX = mLastDownRawX - ev.getRawX();
                        final float deltaY = mDownPositionY - ev.getRawY();
                        final float deltaY = mLastDownRawY - ev.getRawY();
                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;


                        final ViewConfiguration viewConfiguration = ViewConfiguration.get(
                        final ViewConfiguration viewConfiguration = ViewConfiguration.get(
@@ -5804,6 +5722,37 @@ public class Editor {
        public void show() {
        public void show() {
            getHandle().show();
            getHandle().show();


            final long durationSinceCutOrCopy =
                    SystemClock.uptimeMillis() - TextView.sLastCutCopyOrTextChangedTime;

            // Cancel the single tap delayed runnable.
            if (mInsertionActionModeRunnable != null
                    && (mTouchState.isMultiTap() || isCursorInsideEasyCorrectionSpan())) {
                mTextView.removeCallbacks(mInsertionActionModeRunnable);
            }

            // Prepare and schedule the single tap runnable to run exactly after the double tap
            // timeout has passed.
            if (!mTouchState.isMultiTap()
                    && !isCursorInsideEasyCorrectionSpan()
                    && (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION_MS)) {
                if (mTextActionMode == null) {
                    if (mInsertionActionModeRunnable == null) {
                        mInsertionActionModeRunnable = new Runnable() {
                            @Override
                            public void run() {
                                startInsertionActionMode();
                            }
                        };
                    }
                    mTextView.postDelayed(
                            mInsertionActionModeRunnable,
                            ViewConfiguration.getDoubleTapTimeout() + 1);
                }
            }

            getHandle().hideAfterDelay();

            if (mSelectionModifierCursorController != null) {
            if (mSelectionModifierCursorController != null) {
                mSelectionModifierCursorController.hide();
                mSelectionModifierCursorController.hide();
            }
            }
@@ -5870,7 +5819,6 @@ public class Editor {
        // The offsets of that last touch down event. Remembered to start selection there.
        // The offsets of that last touch down event. Remembered to start selection there.
        private int mMinTouchOffset, mMaxTouchOffset;
        private int mMinTouchOffset, mMaxTouchOffset;


        private float mDownPositionX, mDownPositionY;
        private boolean mGestureStayedInTapRegion;
        private boolean mGestureStayedInTapRegion;


        // Where the user first starts the drag motion.
        // Where the user first starts the drag motion.
@@ -5940,13 +5888,18 @@ public class Editor {
        }
        }


        public void enterDrag(int dragAcceleratorMode) {
        public void enterDrag(int dragAcceleratorMode) {
            if (TextView.DEBUG_CURSOR) {
                logCursor("SelectionModifierCursorController: enterDrag",
                        "starting selection drag: mode=%s", dragAcceleratorMode);
            }

            // Just need to init the handles / hide insertion cursor.
            // Just need to init the handles / hide insertion cursor.
            show();
            show();
            mDragAcceleratorMode = dragAcceleratorMode;
            mDragAcceleratorMode = dragAcceleratorMode;
            // Start location of selection.
            // Start location of selection.
            mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX,
            mStartOffset = mTextView.getOffsetForPosition(mTouchState.getLastDownX(),
                    mLastDownPositionY);
                    mTouchState.getLastDownY());
            mLineSelectionIsOn = mTextView.getLineAtCoordinate(mLastDownPositionY);
            mLineSelectionIsOn = mTextView.getLineAtCoordinate(mTouchState.getLastDownY());
            // Don't show the handles until user has lifted finger.
            // Don't show the handles until user has lifted finger.
            hide();
            hide();


@@ -5974,36 +5927,20 @@ public class Editor {
                                eventX, eventY);
                                eventX, eventY);


                        // Double tap detection
                        // Double tap detection
                        if (mGestureStayedInTapRegion) {
                        if (mGestureStayedInTapRegion
                            if (mTapState == TAP_STATE_DOUBLE_TAP
                                && mTouchState.isMultiTapInSameArea()
                                    || mTapState == TAP_STATE_TRIPLE_CLICK) {
                                && (isMouse || isPositionOnText(eventX, eventY))) {
                                final float deltaX = eventX - mDownPositionX;
                                final float deltaY = eventY - mDownPositionY;
                                final float distanceSquared = deltaX * deltaX + deltaY * deltaY;

                                ViewConfiguration viewConfiguration = ViewConfiguration.get(
                                        mTextView.getContext());
                                int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop();
                                boolean stayedInArea =
                                        distanceSquared < doubleTapSlop * doubleTapSlop;

                                if (stayedInArea && (isMouse || isPositionOnText(eventX, eventY))) {
                            if (TextView.DEBUG_CURSOR) {
                            if (TextView.DEBUG_CURSOR) {
                                logCursor("SelectionModifierCursorController: onTouchEvent",
                                logCursor("SelectionModifierCursorController: onTouchEvent",
                                        "ACTION_DOWN: select and start drag");
                                        "ACTION_DOWN: select and start drag");
                            }
                            }
                                    if (mTapState == TAP_STATE_DOUBLE_TAP) {
                            if (mTouchState.isDoubleTap()) {
                                selectCurrentWordAndStartDrag();
                                selectCurrentWordAndStartDrag();
                                    } else if (mTapState == TAP_STATE_TRIPLE_CLICK) {
                            } else if (mTouchState.isTripleClick()) {
                                selectCurrentParagraphAndStartDrag();
                                selectCurrentParagraphAndStartDrag();
                            }
                            }
                            mDiscardNextActionUp = true;
                            mDiscardNextActionUp = true;
                        }
                        }
                            }
                        }

                        mDownPositionX = eventX;
                        mDownPositionY = eventY;
                        mGestureStayedInTapRegion = true;
                        mGestureStayedInTapRegion = true;
                        mHaventMovedEnoughToStartDrag = true;
                        mHaventMovedEnoughToStartDrag = true;
                    }
                    }
@@ -6025,8 +5962,8 @@ public class Editor {
                    final int touchSlop = viewConfig.getScaledTouchSlop();
                    final int touchSlop = viewConfig.getScaledTouchSlop();


                    if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
                    if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
                        final float deltaX = eventX - mDownPositionX;
                        final float deltaX = eventX - mTouchState.getLastDownX();
                        final float deltaY = eventY - mDownPositionY;
                        final float deltaY = eventY - mTouchState.getLastDownY();
                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;


                        if (mGestureStayedInTapRegion) {
                        if (mGestureStayedInTapRegion) {
@@ -7164,7 +7101,7 @@ public class Editor {
        }
        }
    }
    }


    private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
    static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
        if (msgFormat == null) {
        if (msgFormat == null) {
            Log.d(TAG, location);
            Log.d(TAG, location);
        } else {
        } else {
+137 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import static android.widget.Editor.logCursor;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;

import android.annotation.IntDef;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import com.android.internal.annotations.VisibleForTesting;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Helper class used by {@link Editor} to track state for touch events.
 *
 * @hide
 */
@VisibleForTesting(visibility = PACKAGE)
public class EditorTouchState {
    private float mLastDownX, mLastDownY;
    private float mLastUpX, mLastUpY;
    private long mLastUpMillis;

    @IntDef({MultiTapStatus.NONE, MultiTapStatus.FIRST_TAP, MultiTapStatus.DOUBLE_TAP,
            MultiTapStatus.TRIPLE_CLICK})
    @Retention(RetentionPolicy.SOURCE)
    @VisibleForTesting
    public @interface MultiTapStatus {
        int NONE = 0;
        int FIRST_TAP = 1;
        int DOUBLE_TAP = 2;
        int TRIPLE_CLICK = 3; // Only for mouse input.
    }
    @MultiTapStatus
    private int mMultiTapStatus = MultiTapStatus.NONE;
    private boolean mMultiTapInSameArea;

    public float getLastDownX() {
        return mLastDownX;
    }

    public float getLastDownY() {
        return mLastDownY;
    }

    public float getLastUpX() {
        return mLastUpX;
    }

    public float getLastUpY() {
        return mLastUpY;
    }

    public boolean isDoubleTap() {
        return mMultiTapStatus == MultiTapStatus.DOUBLE_TAP;
    }

    public boolean isTripleClick() {
        return mMultiTapStatus == MultiTapStatus.TRIPLE_CLICK;
    }

    public boolean isMultiTap() {
        return mMultiTapStatus == MultiTapStatus.DOUBLE_TAP
                || mMultiTapStatus == MultiTapStatus.TRIPLE_CLICK;
    }

    public boolean isMultiTapInSameArea() {
        return isMultiTap() && mMultiTapInSameArea;
    }

    /**
     * Updates the state based on the new event.
     */
    public void update(MotionEvent event, ViewConfiguration viewConfiguration) {
        final int action = event.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
            final long millisSinceLastUp = event.getEventTime() - mLastUpMillis;
            // Detect double tap and triple click.
            if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout()
                    && (mMultiTapStatus == MultiTapStatus.FIRST_TAP
                    || (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) {
                if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) {
                    mMultiTapStatus = MultiTapStatus.DOUBLE_TAP;
                } else {
                    mMultiTapStatus = MultiTapStatus.TRIPLE_CLICK;
                }
                final float deltaX = event.getX() - mLastDownX;
                final float deltaY = event.getY() - mLastDownY;
                final int distanceSquared = (int) ((deltaX * deltaX) + (deltaY * deltaY));
                int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop();
                mMultiTapInSameArea = distanceSquared < doubleTapSlop * doubleTapSlop;
                if (TextView.DEBUG_CURSOR) {
                    String status = isDoubleTap() ? "double" : "triple";
                    String inSameArea = mMultiTapInSameArea ? "in same area" : "not in same area";
                    logCursor("EditorTouchState", "ACTION_DOWN: %s tap detected, %s",
                            status, inSameArea);
                }
            } else {
                mMultiTapStatus = MultiTapStatus.FIRST_TAP;
                mMultiTapInSameArea = false;
                if (TextView.DEBUG_CURSOR) {
                    logCursor("EditorTouchState", "ACTION_DOWN: first tap detected");
                }
            }
            mLastDownX = event.getX();
            mLastDownY = event.getY();
        } else if (action == MotionEvent.ACTION_UP) {
            if (TextView.DEBUG_CURSOR) {
                logCursor("EditorTouchState", "ACTION_UP");
            }
            mLastUpX = event.getX();
            mLastUpY = event.getY();
            mLastUpMillis = event.getEventTime();
        }
    }
}
+4 −1
Original line number Original line Diff line number Diff line
@@ -10860,7 +10860,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    @Override
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    public boolean onTouchEvent(MotionEvent event) {
        if (DEBUG_CURSOR) {
        if (DEBUG_CURSOR) {
            logCursor("onTouchEvent", MotionEvent.actionToString(event.getActionMasked()));
            logCursor("onTouchEvent", "%d: %s (%f,%f)",
                    event.getSequenceNumber(),
                    MotionEvent.actionToString(event.getActionMasked()),
                    event.getX(), event.getY());
        }
        }
        final int action = event.getActionMasked();
        final int action = event.getActionMasked();
+229 −0

File added.

Preview size limit exceeded, changes collapsed.