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

Commit 7094198a authored by Nikita Dubrovsky's avatar Nikita Dubrovsky Committed by Android (Google) Code Review
Browse files

Merge "Add DeviceConfig flag for drag direction angle threshold for cursor drag" into rvc-qpr-dev

parents 2cdf843c 81fa5e43
Loading
Loading
Loading
Loading
+18 −4
Original line number Diff line number Diff line
@@ -387,6 +387,7 @@ public class Editor {
    private final SuggestionHelper mSuggestionHelper = new SuggestionHelper();

    private boolean mFlagCursorDragFromAnywhereEnabled;
    private float mCursorDragDirectionMinXYRatio;
    private boolean mFlagInsertionHandleGesturesEnabled;

    // Specifies whether the new magnifier (with fish-eye effect) is enabled.
@@ -423,6 +424,11 @@ public class Editor {
        mFlagCursorDragFromAnywhereEnabled = AppGlobals.getIntCoreSetting(
                WidgetFlags.KEY_ENABLE_CURSOR_DRAG_FROM_ANYWHERE,
                WidgetFlags.ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT ? 1 : 0) != 0;
        final int cursorDragMinAngleFromVertical = AppGlobals.getIntCoreSetting(
                WidgetFlags.KEY_CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL,
                WidgetFlags.CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL_DEFAULT);
        mCursorDragDirectionMinXYRatio = EditorTouchState.getXYRatio(
                cursorDragMinAngleFromVertical);
        mFlagInsertionHandleGesturesEnabled = AppGlobals.getIntCoreSetting(
                WidgetFlags.KEY_ENABLE_INSERTION_HANDLE_GESTURES,
                WidgetFlags.ENABLE_INSERTION_HANDLE_GESTURES_DEFAULT ? 1 : 0) != 0;
@@ -432,6 +438,8 @@ public class Editor {
        if (TextView.DEBUG_CURSOR) {
            logCursor("Editor", "Cursor drag from anywhere is %s.",
                    mFlagCursorDragFromAnywhereEnabled ? "enabled" : "disabled");
            logCursor("Editor", "Cursor drag min angle from vertical is %d (= %f x/y ratio)",
                    cursorDragMinAngleFromVertical, mCursorDragDirectionMinXYRatio);
            logCursor("Editor", "Insertion handle gestures is %s.",
                    mFlagInsertionHandleGesturesEnabled ? "enabled" : "disabled");
            logCursor("Editor", "New magnifier is %s.",
@@ -457,6 +465,11 @@ public class Editor {
        mFlagCursorDragFromAnywhereEnabled = enabled;
    }

    @VisibleForTesting
    public void setCursorDragMinAngleFromVertical(int degreesFromVertical) {
        mCursorDragDirectionMinXYRatio = EditorTouchState.getXYRatio(degreesFromVertical);
    }

    @VisibleForTesting
    public boolean getFlagInsertionHandleGesturesEnabled() {
        return mFlagInsertionHandleGesturesEnabled;
@@ -6132,7 +6145,8 @@ public class Editor {
                            && mTextView.getLayout() != null
                            && mTextView.isFocused()
                            && mTouchState.isMovedEnoughForDrag()
                                && !mTouchState.isDragCloseToVertical()) {
                            && (mTouchState.getInitialDragDirectionXYRatio()
                            > mCursorDragDirectionMinXYRatio || mTouchState.isOnHandle())) {
                        startCursorDrag(event);
                    }
                    break;
+46 −9
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ public class EditorTouchState {
    private boolean mMultiTapInSameArea;

    private boolean mMovedEnoughForDrag;
    private boolean mIsDragCloseToVertical;
    private float mInitialDragDirectionXYRatio;

    public float getLastDownX() {
        return mLastDownX;
@@ -98,8 +98,23 @@ public class EditorTouchState {
        return mMovedEnoughForDrag;
    }

    public boolean isDragCloseToVertical() {
        return mIsDragCloseToVertical && !mIsOnHandle;
    /**
     * When {@link #isMovedEnoughForDrag()} is {@code true}, this function returns the x/y ratio for
     * the initial drag direction. Smaller values indicate that the direction is closer to vertical,
     * while larger values indicate that the direction is closer to horizontal. For example:
     * <ul>
     *     <li>if the drag direction is exactly vertical, this returns 0
     *     <li>if the drag direction is exactly horizontal, this returns {@link Float#MAX_VALUE}
     *     <li>if the drag direction is 45 deg from vertical, this returns 1
     *     <li>if the drag direction is 30 deg from vertical, this returns 0.58 (x delta is smaller
     *     than y delta)
     *     <li>if the drag direction is 60 deg from vertical, this returns 1.73 (x delta is bigger
     *     than y delta)
     * </ul>
     * This function never returns negative values, regardless of the direction of the drag.
     */
    public float getInitialDragDirectionXYRatio() {
        return mInitialDragDirectionXYRatio;
    }

    public void setIsOnHandle(boolean onHandle) {
@@ -155,7 +170,7 @@ public class EditorTouchState {
            mLastDownY = event.getY();
            mLastDownMillis = event.getEventTime();
            mMovedEnoughForDrag = false;
            mIsDragCloseToVertical = false;
            mInitialDragDirectionXYRatio = 0.0f;
        } else if (action == MotionEvent.ACTION_UP) {
            if (TextView.DEBUG_CURSOR) {
                logCursor("EditorTouchState", "ACTION_UP");
@@ -164,7 +179,7 @@ public class EditorTouchState {
            mLastUpY = event.getY();
            mLastUpMillis = event.getEventTime();
            mMovedEnoughForDrag = false;
            mIsDragCloseToVertical = false;
            mInitialDragDirectionXYRatio = 0.0f;
        } else if (action == MotionEvent.ACTION_MOVE) {
            if (!mMovedEnoughForDrag) {
                float deltaX = event.getX() - mLastDownX;
@@ -174,9 +189,8 @@ public class EditorTouchState {
                int touchSlop = config.getScaledTouchSlop();
                mMovedEnoughForDrag = distanceSquared > touchSlop * touchSlop;
                if (mMovedEnoughForDrag) {
                    // If the direction of the swipe motion is within 45 degrees of vertical, it is
                    // considered a vertical drag.
                    mIsDragCloseToVertical = Math.abs(deltaX) <= Math.abs(deltaY);
                    mInitialDragDirectionXYRatio = (deltaY == 0) ? Float.MAX_VALUE :
                            Math.abs(deltaX / deltaY);
                }
            }
        } else if (action == MotionEvent.ACTION_CANCEL) {
@@ -185,7 +199,7 @@ public class EditorTouchState {
            mMultiTapStatus = MultiTapStatus.NONE;
            mMultiTapInSameArea = false;
            mMovedEnoughForDrag = false;
            mIsDragCloseToVertical = false;
            mInitialDragDirectionXYRatio = 0.0f;
        }
    }

@@ -201,4 +215,27 @@ public class EditorTouchState {
        float distanceSquared = (deltaX * deltaX) + (deltaY * deltaY);
        return distanceSquared <= maxDistance * maxDistance;
    }

    /**
     * Returns the x/y ratio corresponding to the given angle relative to vertical. Smaller angle
     * values (ie, closer to vertical) will result in a smaller x/y ratio. For example:
     * <ul>
     *     <li>if the angle is 45 deg, the ratio is 1
     *     <li>if the angle is 30 deg, the ratio is 0.58 (x delta is smaller than y delta)
     *     <li>if the angle is 60 deg, the ratio is 1.73 (x delta is bigger than y delta)
     * </ul>
     * If the passed-in value is <= 0, this function returns 0. If the passed-in value is >= 90,
     * this function returns {@link Float#MAX_VALUE}.
     *
     * @see #getInitialDragDirectionXYRatio()
     */
    public static float getXYRatio(int angleFromVerticalInDegrees) {
        if (angleFromVerticalInDegrees <= 0) {
            return 0.0f;
        }
        if (angleFromVerticalInDegrees >= 90) {
            return Float.MAX_VALUE;
        }
        return (float) Math.tan(Math.toRadians(angleFromVerticalInDegrees));
    }
}
+22 −0
Original line number Diff line number Diff line
@@ -40,6 +40,28 @@ public final class WidgetFlags {
     */
    public static final boolean ENABLE_CURSOR_DRAG_FROM_ANYWHERE_DEFAULT = true;

    /**
     * Threshold for the direction of a swipe gesture in order for it to be handled as a cursor drag
     * rather than a scroll. The direction angle of the swipe gesture must exceed this value in
     * order to trigger cursor drag; otherwise, the swipe will be assumed to be a scroll gesture.
     * The value units for this flag is degrees and the valid range is [0,90] inclusive. If a value
     * < 0 is set, 0 will be used instead; if a value > 90 is set, 90 will be used instead.
     */
    public static final String CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL =
            "CursorControlFeature__min_angle_from_vertical_to_start_cursor_drag";

    /**
     * The key used in app core settings for the flag
     * {@link #CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL}.
     */
    public static final String KEY_CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL =
            "widget__min_angle_from_vertical_to_start_cursor_drag";

    /**
     * Default value for the flag {@link #CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL}.
     */
    public static final int CURSOR_DRAG_MIN_ANGLE_FROM_VERTICAL_DEFAULT = 45;

    /**
     * The flag of finger-to-cursor distance in DP for cursor dragging.
     * The value unit is DP and the range is {0..100}. If the value is out of range, the legacy
+32 −0
Original line number Diff line number Diff line
@@ -200,6 +200,38 @@ public class EditorCursorDragTest {
        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
    }

    @Test
    public void testCursorDrag_diagonal_thresholdConfig() throws Throwable {
        TextView tv = mActivity.findViewById(R.id.textview);
        Editor editor = tv.getEditorForTesting();

        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= 9; i++) {
            sb.append("here is some text").append(i).append("\n");
        }
        sb.append(Strings.repeat("abcdefghij\n", 400)).append("Last");
        String text = sb.toString();
        onView(withId(R.id.textview)).perform(replaceText(text));

        int index = text.indexOf("text9");
        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));

        // Configure the drag direction threshold to require the drag to be exactly horizontal. With
        // this set, a swipe that is slightly off horizontal should not trigger cursor drag.
        editor.setCursorDragMinAngleFromVertical(90);
        int startIdx = text.indexOf("5");
        int endIdx = text.indexOf("here is some text3");
        onView(withId(R.id.textview)).perform(dragOnText(startIdx, endIdx));
        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));

        // Configure the drag direction threshold to require the drag to be 45 degrees or more from
        // vertical. With this set, the same swipe gesture as above should now trigger cursor drag.
        editor.setCursorDragMinAngleFromVertical(45);
        onView(withId(R.id.textview)).perform(dragOnText(startIdx, endIdx));
        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(endIdx));
    }

    @Test
    public void testCursorDrag_vertical_whenTextViewContentsFitOnScreen() throws Throwable {
        String text = "012345_aaa\n"
+91 −14
Original line number Diff line number Diff line
@@ -165,7 +165,7 @@ public class EditorTouchStateTest {
        long event2Time = 1001;
        MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
        mTouchState.update(event2, mConfig);
        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
        assertDrag(mTouchState, 20f, 30f, 0, 0, 180f);

        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
        long event3Time = 5000;
@@ -280,7 +280,7 @@ public class EditorTouchStateTest {
        long event3Time = 1002;
        MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
        mTouchState.update(event3, mConfig);
        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
        assertDrag(mTouchState, 20f, 30f, 0, 0, Float.MAX_VALUE);

        // Simulate an ACTION_UP event.
        long event4Time = 1003;
@@ -301,15 +301,15 @@ public class EditorTouchStateTest {
        long event2Time = 1002;
        MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
        mTouchState.update(event2, mConfig);
        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 174f);

        // Simulate another ACTION_MOVE event that is horizontal from the original down event.
        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
        // initial direction of movement.
        // The drag direction ratio should NOT change since it should only reflect the initial
        // direction of movement.
        long event3Time = 1003;
        MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
        mTouchState.update(event3, mConfig);
        assertDrag(mTouchState, 0f, 0f, 0, 0, true);
        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 174f);

        // Simulate an ACTION_UP event.
        long event4Time = 1004;
@@ -330,15 +330,15 @@ public class EditorTouchStateTest {
        long event2Time = 1002;
        MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 90f);
        mTouchState.update(event2, mConfig);
        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 90f);

        // Simulate another ACTION_MOVE event that is vertical from the original down event.
        // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
        // initial direction of movement.
        // The drag direction ratio should NOT change since it should only reflect the initial
        // direction of movement.
        long event3Time = 1003;
        MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
        mTouchState.update(event3, mConfig);
        assertDrag(mTouchState, 0f, 0f, 0, 0, false);
        assertDrag(mTouchState, 0f, 0f, 0, 0, 100f / 90f);

        // Simulate an ACTION_UP event.
        long event4Time = 1004;
@@ -374,7 +374,7 @@ public class EditorTouchStateTest {
        long event2Time = 1002;
        MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
        mTouchState.update(event2, mConfig);
        assertDrag(mTouchState, 20f, 30f, 0, 0, false);
        assertDrag(mTouchState, 20f, 30f, 0, 0, Float.MAX_VALUE);

        // Simulate an ACTION_CANCEL event.
        long event3Time = 1003;
@@ -411,6 +411,84 @@ public class EditorTouchStateTest {
        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
    }

    @Test
    public void testGetXYRatio() throws Exception {
        doTestGetXYRatio(-1, 0.0f);
        doTestGetXYRatio(0, 0.0f);
        doTestGetXYRatio(30, 0.58f);
        doTestGetXYRatio(45, 1.0f);
        doTestGetXYRatio(60, 1.73f);
        doTestGetXYRatio(90, Float.MAX_VALUE);
        doTestGetXYRatio(91, Float.MAX_VALUE);
    }

    private void doTestGetXYRatio(int angleFromVerticalInDegrees, float expectedXYRatioRounded) {
        float result = EditorTouchState.getXYRatio(angleFromVerticalInDegrees);
        String msg = String.format(
                "%d deg should give an x/y ratio of %f; actual unrounded result is %f",
                angleFromVerticalInDegrees, expectedXYRatioRounded, result);
        float roundedResult = (result == 0.0f || result == Float.MAX_VALUE) ? result :
                Math.round(result * 100) / 100f;
        assertThat(msg, roundedResult, is(expectedXYRatioRounded));
    }

    @Test
    public void testUpdate_dragDirection() throws Exception {
        // Simulate moving straight up.
        doTestDragDirection(100f, 100f, 100f, 50f, 0f);

        // Simulate moving straight down.
        doTestDragDirection(100f, 100f, 100f, 150f, 0f);

        // Simulate moving straight left.
        doTestDragDirection(100f, 100f, 50f, 100f, Float.MAX_VALUE);

        // Simulate moving straight right.
        doTestDragDirection(100f, 100f, 150f, 100f, Float.MAX_VALUE);

        // Simulate moving up and right, < 45 deg from vertical.
        doTestDragDirection(100f, 100f, 110f, 50f, 10f / 50f);

        // Simulate moving up and right, > 45 deg from vertical.
        doTestDragDirection(100f, 100f, 150f, 90f, 50f / 10f);

        // Simulate moving down and right, < 45 deg from vertical.
        doTestDragDirection(100f, 100f, 110f, 150f, 10f / 50f);

        // Simulate moving down and right, > 45 deg from vertical.
        doTestDragDirection(100f, 100f, 150f, 110f, 50f / 10f);

        // Simulate moving down and left, < 45 deg from vertical.
        doTestDragDirection(100f, 100f, 90f, 150f, 10f / 50f);

        // Simulate moving down and left, > 45 deg from vertical.
        doTestDragDirection(100f, 100f, 50f, 110f, 50f / 10f);

        // Simulate moving up and left, < 45 deg from vertical.
        doTestDragDirection(100f, 100f, 90f, 50f, 10f / 50f);

        // Simulate moving up and left, > 45 deg from vertical.
        doTestDragDirection(100f, 100f, 50f, 90f, 50f / 10f);
    }

    private void doTestDragDirection(float downX, float downY, float moveX, float moveY,
            float expectedInitialDragDirectionXYRatio) {
        EditorTouchState touchState = new EditorTouchState();

        // Simulate an ACTION_DOWN event.
        long event1Time = 1001;
        MotionEvent event1 = downEvent(event1Time, event1Time, downX, downY);
        touchState.update(event1, mConfig);

        // Simulate an ACTION_MOVE event.
        long event2Time = 1002;
        MotionEvent event2 = moveEvent(event1Time, event2Time, moveX, moveY);
        touchState.update(event2, mConfig);
        String msg = String.format("(%.0f,%.0f)=>(%.0f,%.0f)", downX, downY, moveX, moveY);
        assertThat(msg, touchState.getInitialDragDirectionXYRatio(),
                is(expectedInitialDragDirectionXYRatio));
    }

    private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
    }
@@ -441,7 +519,7 @@ public class EditorTouchStateTest {
    }

    private static void assertDrag(EditorTouchState touchState, float lastDownX,
            float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
            float lastDownY, float lastUpX, float lastUpY, float initialDragDirectionXYRatio) {
        assertThat(touchState.getLastDownX(), is(lastDownX));
        assertThat(touchState.getLastDownY(), is(lastDownY));
        assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -451,7 +529,7 @@ public class EditorTouchStateTest {
        assertThat(touchState.isMultiTap(), is(false));
        assertThat(touchState.isMultiTapInSameArea(), is(false));
        assertThat(touchState.isMovedEnoughForDrag(), is(true));
        assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
        assertThat(touchState.getInitialDragDirectionXYRatio(), is(initialDragDirectionXYRatio));
    }

    private static void assertMultiTap(EditorTouchState touchState,
@@ -467,6 +545,5 @@ public class EditorTouchStateTest {
                || multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
        assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
        assertThat(touchState.isMovedEnoughForDrag(), is(false));
        assertThat(touchState.isDragCloseToVertical(), is(false));
    }
}
Loading