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

Commit e709f0a4 authored by Justin Ghan's avatar Justin Ghan
Browse files

Handwriting gesture line margin

Point-based handwriting gestures are supported for points within text
line bounds. With this change, they will also be supported for points
which are outside the line bounds but within a margin of the line bounds.

The margin is added to ViewConfiguration so that the value can be
accessed by other UI toolkits so that they can implement the same
behavior.

The dimension resource is added as a @SystemApi so that OEMs may
override the value.

Bug: 243967209
Test: atest android.widget.cts.TextViewHandwritingGestureTest
Change-Id: I0e64f20c670808580346a485d06a0da0f21d3ed2
parent 7fcb9a6c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -50911,6 +50911,7 @@ package android.view {
    method public int getScaledDoubleTapSlop();
    method public int getScaledEdgeSlop();
    method public int getScaledFadingEdgeLength();
    method public int getScaledHandwritingGestureLineMargin();
    method public int getScaledHandwritingSlop();
    method public float getScaledHorizontalScrollFactor();
    method public int getScaledHoverSlop();
+1 −0
Original line number Diff line number Diff line
@@ -401,6 +401,7 @@ package android {
  public static final class R.dimen {
    field public static final int config_restrictedIconSize = 17104903; // 0x1050007
    field public static final int config_viewConfigurationHandwritingGestureLineMargin;
  }
  public static final class R.drawable {
+18 −0
Original line number Diff line number Diff line
@@ -217,6 +217,11 @@ public class ViewConfiguration {
     */
    private static final int WINDOW_TOUCH_SLOP = 16;

    /**
     * Margin in dips around text line bounds where stylus handwriting gestures should be supported.
     */
    private static final int HANDWRITING_GESTURE_LINE_MARGIN = 16;

    /**
     * Minimum velocity to initiate a fling, as measured in dips per second
     */
@@ -338,6 +343,7 @@ public class ViewConfiguration {
    private final int mPagingTouchSlop;
    private final int mDoubleTapSlop;
    private final int mWindowTouchSlop;
    private final int mHandwritingGestureLineMargin;
    private final float mAmbiguousGestureMultiplier;
    private final int mMaximumDrawingCacheSize;
    private final int mOverscrollDistance;
@@ -381,6 +387,7 @@ public class ViewConfiguration {
        mPagingTouchSlop = PAGING_TOUCH_SLOP;
        mDoubleTapSlop = DOUBLE_TAP_SLOP;
        mWindowTouchSlop = WINDOW_TOUCH_SLOP;
        mHandwritingGestureLineMargin = HANDWRITING_GESTURE_LINE_MARGIN;
        mAmbiguousGestureMultiplier = AMBIGUOUS_GESTURE_MULTIPLIER;
        //noinspection deprecation
        mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -490,6 +497,9 @@ public class ViewConfiguration {

        mDoubleTapTouchSlop = mTouchSlop;

        mHandwritingGestureLineMargin = res.getDimensionPixelSize(
                com.android.internal.R.dimen.config_viewConfigurationHandwritingGestureLineMargin);

        mMinimumFlingVelocity = res.getDimensionPixelSize(
                com.android.internal.R.dimen.config_viewMinFlingVelocity);
        mMaximumFlingVelocity = res.getDimensionPixelSize(
@@ -795,6 +805,14 @@ public class ViewConfiguration {
        return mDoubleTapSlop;
    }

    /**
     * @return margin in pixels around text line bounds where stylus handwriting gestures should be
     *     supported.
     */
    public int getScaledHandwritingGestureLineMargin() {
        return mHandwritingGestureLineMargin;
    }

    /**
     * Interval for dispatching a recurring accessibility event in milliseconds.
     * This interval guarantees that a recurring event will be send at most once
+44 −31
Original line number Diff line number Diff line
@@ -9391,12 +9391,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    /** @hide */
    public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) {
        PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
        int line = mLayout.getLineForVertical((int) point.y);
        if (point.y < mLayout.getLineTop(line)
                || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
            return handleGestureFailure(gesture);
        }
        if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
        int line = getLineForHandwritingGesture(point);
        if (line == -1) {
            return handleGestureFailure(gesture);
        }
        int offset = mLayout.getOffsetForHorizontal(line, point.x);
@@ -9412,27 +9408,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint());
        PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint());
        // The operation should be applied to the first line of text touched by the line joining
        // the points.
        int yMin = (int) Math.min(startPoint.y, endPoint.y);
        int yMax = (int) Math.max(startPoint.y, endPoint.y);
        int line = mLayout.getLineForVertical(yMin);
        if (yMax < mLayout.getLineTop(line)) {
            // Both points are above the top of the first line.
        // The operation should be applied to the first line of text containing one of the points.
        int startPointLine = getLineForHandwritingGesture(startPoint);
        int endPointLine = getLineForHandwritingGesture(endPoint);
        int line;
        if (startPointLine == -1) {
            if (endPointLine == -1) {
                return handleGestureFailure(gesture);
            }
        if (yMin > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
            if (line == mLayout.getLineCount() - 1 || yMax < mLayout.getLineTop(line + 1)) {
                // The points are below the last line, or they are between two lines.
                return handleGestureFailure(gesture);
            line = endPointLine;
        } else {
                // Apply the operation to the next line.
                line++;
            }
        }
        if (Math.max(startPoint.x, endPoint.x) < mLayout.getLineLeft(line)
                || Math.min(startPoint.x, endPoint.x) > mLayout.getLineRight(line)) {
            return handleGestureFailure(gesture);
            line = (endPointLine == -1) ? startPointLine : Math.min(startPointLine, endPointLine);
        }
        // The operation should be applied to all characters touched by the line joining the points.
@@ -9479,12 +9465,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) {
        PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint());
        int line = mLayout.getLineForVertical((int) point.y);
        if (point.y < mLayout.getLineTop(line)
                || point.y > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)) {
            return handleGestureFailure(gesture);
        }
        if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) {
        int line = getLineForHandwritingGesture(point);
        if (line == -1) {
            return handleGestureFailure(gesture);
        }
@@ -9529,6 +9511,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
    }
    /**
     * Returns the closest line such that the point is either inside the line bounds or within
     * {@link ViewConfiguration#getScaledHandwritingGestureLineMargin} of the line bounds. Returns
     * -1 if the point is not within the margin of any line bounds.
     */
    private int getLineForHandwritingGesture(PointF point) {
        int line = mLayout.getLineForVertical((int) point.y);
        int lineMargin = ViewConfiguration.get(mContext).getScaledHandwritingGestureLineMargin();
        if (line < mLayout.getLineCount() - 1
                && point.y > mLayout.getLineBottom(line) - lineMargin
                && point.y
                        > (mLayout.getLineBottom(line, false) + mLayout.getLineBottom(line)) / 2f) {
            // If a point is in the space between line i and line (i + 1), Layout#getLineForVertical
            // returns i. If the point is within lineMargin of line (i + 1), and closer to line
            // (i + 1) than line i, then the gesture operation should be applied to line (i + 1).
            line++;
        } else if (point.y < mLayout.getLineTop(line) - lineMargin
                || point.y
                        > mLayout.getLineBottom(line, /* includeLineSpacing= */ false)
                                + lineMargin) {
            // The point is not within lineMargin of a line.
            return -1;
        }
        if (point.x < mLayout.getLineLeft(line) - lineMargin
                || point.x > mLayout.getLineRight(line) + lineMargin) {
            // The point is not within lineMargin of a line.
            return -1;
        }
        return line;
    }
    @Nullable
    private Range<Integer> getRangeForRect(@NonNull RectF area, int granularity) {
        SegmentFinder segmentFinder;
+3 −0
Original line number Diff line number Diff line
@@ -2610,6 +2610,9 @@
         movement threshold under which hover is considered "stationary". -->
    <dimen name="config_viewConfigurationHoverSlop">4dp</dimen>

    <!-- Margin around text line bounds where stylus handwriting gestures should be supported. -->
    <dimen name="config_viewConfigurationHandwritingGestureLineMargin">16dp</dimen>

    <!-- Multiplier for gesture thresholds when a MotionEvent classification is ambiguous. -->
    <item name="config_ambiguousGestureMultiplier" format="float" type="dimen">2.0</item>

Loading