From e709f0a4edb1069dd87d69848b1c1071d56f6fdf Mon Sep 17 00:00:00 2001 From: Justin Ghan Date: Wed, 28 Sep 2022 15:28:24 -0700 Subject: [PATCH] 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 --- core/api/current.txt | 1 + core/api/system-current.txt | 1 + core/java/android/view/ViewConfiguration.java | 18 +++++ core/java/android/widget/TextView.java | 75 +++++++++++-------- core/res/res/values/config.xml | 3 + core/res/res/values/public-staging.xml | 2 + 6 files changed, 69 insertions(+), 31 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 7bdaa2b8da681..bbc35dfd787ec 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 761d9fa3841f5..80e19f9377b49 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -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 { diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index e5a535b59eb6b..f51d9bacc0a58 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -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 diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ce5365acf1f40..a2e9faa81eeb2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -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. - 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. + // 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); - } 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; + } else { + 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 getRangeForRect(@NonNull RectF area, int granularity) { SegmentFinder segmentFinder; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 90d84ddbccdb1..ddc74add54a2f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2610,6 +2610,9 @@ movement threshold under which hover is considered "stationary". --> 4dp + + 16dp + 2.0 diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 89741effb9452..d03d20697788c 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -131,6 +131,8 @@ + +