Loading core/java/android/text/flags/flags.aconfig +11 −1 Original line number Diff line number Diff line Loading @@ -267,3 +267,13 @@ flag { purpose: PURPOSE_BUGFIX } } flag { name: "handwriting_gesture_with_transformation" namespace: "text" description: "Fix handwriting gesture is not working when view has transformation." bug: "342619429" metadata { purpose: PURPOSE_BUGFIX } } core/java/android/widget/TextView.java +169 −43 Original line number Diff line number Diff line Loading @@ -28,10 +28,10 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import android.R; import android.annotation.CallSuper; Loading Loading @@ -937,6 +937,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private TextPaint mTempTextPaint; private Object mTempCursor; private Matrix mTempMatrix; @UnsupportedAppUsage private BoringLayout.Metrics mBoring; Loading Loading @@ -12106,6 +12107,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private PointF convertFromScreenToContentCoordinates(PointF point) { if (Flags.handwritingGestureWithTransformation()) { if (mTempMatrix == null) { mTempMatrix = new Matrix(); } Matrix matrix = mTempMatrix; matrix.reset(); transformMatrixToLocal(matrix); matrix.postTranslate( -viewportToContentHorizontalOffset(), -viewportToContentVerticalOffset() ); float[] copy = new float[] { point.x, point.y }; matrix.mapPoints(copy); return new PointF(copy[0], copy[1]); } int[] screenToViewport = getLocationOnScreen(); PointF copy = new PointF(point); copy.offset( Loading @@ -12115,6 +12132,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private RectF convertFromScreenToContentCoordinates(RectF rect) { if (Flags.handwritingGestureWithTransformation()) { if (mTempMatrix == null) { mTempMatrix = new Matrix(); } Matrix matrix = mTempMatrix; matrix.reset(); transformMatrixToLocal(matrix); matrix.postTranslate( -viewportToContentHorizontalOffset(), -viewportToContentVerticalOffset() ); RectF copy = new RectF(rect); matrix.mapRect(copy); return copy; } int[] screenToViewport = getLocationOnScreen(); RectF copy = new RectF(rect); copy.offset( Loading Loading @@ -14279,6 +14312,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** * Don't use, it returns wrong result when the view is scaled. This method can be removed once * Flags.handwritingGestureWithTransformation is enabled. * Assume * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates. * This method obtains the view's visible rectangle whereas the method * {@link #getContentVisibleRect} returns the text layout's visible rectangle. Loading @@ -14299,6 +14335,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** * Don't use, it returns wrong result when view is scaled. This method can be removed once * Flags.handwritingGestureWithTransformation is enabled. * Helper method to set {@code rect} to the text content's non-clipped area in the view's * coordinates. * Loading @@ -14314,6 +14352,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); } private boolean getEditorAndHandwritingBounds(@NonNull RectF editorBounds, @Nullable RectF handwritingBounds) { if (mTempRect == null) { mTempRect = new Rect(); } Rect rect = mTempRect; if (!getGlobalVisibleRect(rect)) { return false; } if (mTempMatrix == null) { mTempMatrix = new Matrix(); } Matrix matrix = mTempMatrix; matrix.reset(); transformMatrixToLocal(matrix); editorBounds.set(rect); // When the view has transformations like scaleX/scaleY computing the global visible // rectangle will already apply the transformations. The getLocalVisibleRect only offsets // the global rectangle to local. And the result is wrong the View is scaled. // // This approach use the local transformation matrix to map the global rectangle to // local instead. // // Note: it doesn't work well with rotation. Because Rect must be // axis-aligned, when a rotated Rect becomes quadrilateral, the quadrilateral's // bounding box is stored at Rect instead. It makes the returned Rect larger than // the correct size. matrix.mapRect(editorBounds); if (handwritingBounds != null) { // Similar to editorBounds, handwritingBounds must be computed in global coordinates // and then converted back to local coordinates. Otherwise, if the view is scaled, // the handwritingBoundsOffsets are also scaled, which is not the expected behavior. handwritingBounds.top = rect.top - getHandwritingBoundsOffsetTop(); handwritingBounds.left = rect.left - getHandwritingBoundsOffsetLeft(); handwritingBounds.bottom = rect.bottom + getHandwritingBoundsOffsetBottom(); handwritingBounds.right = rect.right + getHandwritingBoundsOffsetRight(); matrix.mapRect(handwritingBounds); } return true; } private boolean getContentVisibleRect(RectF rect) { if (!getEditorAndHandwritingBounds(rect, /* handwritingBounds= */null)) { return false; } // Clip the view's visible rect with the text layout's visible rect. return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(), getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); } /** * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} * Loading @@ -14333,9 +14423,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // character bounds in this case yet. return; } final RectF visibleRect = new RectF(); if (Flags.handwritingGestureWithTransformation()) { getContentVisibleRect(visibleRect); } else { final Rect rect = new Rect(); getContentVisibleRect(rect); final RectF visibleRect = new RectF(rect); visibleRect.set(rect); } final float[] characterBounds = getCharacterBounds(startIndex, endIndex, viewportToContentHorizontalOffset, viewportToContentVerticalOffset); Loading Loading @@ -14438,24 +14534,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener builder.setMatrix(viewToScreenMatrix); if (includeEditorBounds) { final RectF editorBounds = new RectF(); final RectF handwritingBounds = new RectF(); if (Flags.handwritingGestureWithTransformation()) { getEditorAndHandwritingBounds(editorBounds, handwritingBounds); } else { if (mTempRect == null) { mTempRect = new Rect(); } final Rect bounds = mTempRect; final RectF editorBounds; final RectF handwritingBounds; // If the editor is not visible at all, return empty rectangles. We still need to // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo. if (getViewVisibleRect(bounds)) { editorBounds = new RectF(bounds); handwritingBounds = new RectF(editorBounds); editorBounds.set(bounds); handwritingBounds.set(editorBounds); handwritingBounds.top -= getHandwritingBoundsOffsetTop(); handwritingBounds.left -= getHandwritingBoundsOffsetLeft(); handwritingBounds.bottom += getHandwritingBoundsOffsetBottom(); handwritingBounds.right += getHandwritingBoundsOffsetRight(); } else { // The editor is not visible at all, return empty rectangles. We still need to // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo. editorBounds = new RectF(); handwritingBounds = new RectF(); } } EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) Loading Loading @@ -14533,6 +14631,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (includeVisibleLineBounds) { if (Flags.handwritingGestureWithTransformation()) { RectF visibleRect = new RectF(); if (getContentVisibleRect(visibleRect)) { // Subtract the viewportToContentVerticalOffset to convert the view // coordinates to layout coordinates. final float visibleTop = visibleRect.top - viewportToContentVerticalOffset; final float visibleBottom = visibleRect.bottom - viewportToContentVerticalOffset; final int firstLine = layout.getLineForVertical((int) Math.floor(visibleTop)); final int lastLine = layout.getLineForVertical((int) Math.ceil(visibleBottom)); for (int line = firstLine; line <= lastLine; ++line) { final float left = layout.getLineLeft(line) + viewportToContentHorizontalOffset; final float top = layout.getLineTop(line) + viewportToContentVerticalOffset; final float right = layout.getLineRight(line) + viewportToContentHorizontalOffset; final float bottom = layout.getLineBottom(line, false) + viewportToContentVerticalOffset; builder.addVisibleLineBounds(left, top, right, bottom); } } } else { final Rect visibleRect = new Rect(); if (getContentVisibleRect(visibleRect)) { // Subtract the viewportToContentVerticalOffset to convert the view Loading Loading @@ -14560,6 +14685,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } } if (includeTextAppearance) { builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this)); Loading
core/java/android/text/flags/flags.aconfig +11 −1 Original line number Diff line number Diff line Loading @@ -267,3 +267,13 @@ flag { purpose: PURPOSE_BUGFIX } } flag { name: "handwriting_gesture_with_transformation" namespace: "text" description: "Fix handwriting gesture is not working when view has transformation." bug: "342619429" metadata { purpose: PURPOSE_BUGFIX } }
core/java/android/widget/TextView.java +169 −43 Original line number Diff line number Diff line Loading @@ -28,10 +28,10 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import android.R; import android.annotation.CallSuper; Loading Loading @@ -937,6 +937,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private TextPaint mTempTextPaint; private Object mTempCursor; private Matrix mTempMatrix; @UnsupportedAppUsage private BoringLayout.Metrics mBoring; Loading Loading @@ -12106,6 +12107,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private PointF convertFromScreenToContentCoordinates(PointF point) { if (Flags.handwritingGestureWithTransformation()) { if (mTempMatrix == null) { mTempMatrix = new Matrix(); } Matrix matrix = mTempMatrix; matrix.reset(); transformMatrixToLocal(matrix); matrix.postTranslate( -viewportToContentHorizontalOffset(), -viewportToContentVerticalOffset() ); float[] copy = new float[] { point.x, point.y }; matrix.mapPoints(copy); return new PointF(copy[0], copy[1]); } int[] screenToViewport = getLocationOnScreen(); PointF copy = new PointF(point); copy.offset( Loading @@ -12115,6 +12132,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private RectF convertFromScreenToContentCoordinates(RectF rect) { if (Flags.handwritingGestureWithTransformation()) { if (mTempMatrix == null) { mTempMatrix = new Matrix(); } Matrix matrix = mTempMatrix; matrix.reset(); transformMatrixToLocal(matrix); matrix.postTranslate( -viewportToContentHorizontalOffset(), -viewportToContentVerticalOffset() ); RectF copy = new RectF(rect); matrix.mapRect(copy); return copy; } int[] screenToViewport = getLocationOnScreen(); RectF copy = new RectF(rect); copy.offset( Loading Loading @@ -14279,6 +14312,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** * Don't use, it returns wrong result when the view is scaled. This method can be removed once * Flags.handwritingGestureWithTransformation is enabled. * Assume * Helper method to set {@code rect} to this TextView's non-clipped area in its own coordinates. * This method obtains the view's visible rectangle whereas the method * {@link #getContentVisibleRect} returns the text layout's visible rectangle. Loading @@ -14299,6 +14335,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** * Don't use, it returns wrong result when view is scaled. This method can be removed once * Flags.handwritingGestureWithTransformation is enabled. * Helper method to set {@code rect} to the text content's non-clipped area in the view's * coordinates. * Loading @@ -14314,6 +14352,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); } private boolean getEditorAndHandwritingBounds(@NonNull RectF editorBounds, @Nullable RectF handwritingBounds) { if (mTempRect == null) { mTempRect = new Rect(); } Rect rect = mTempRect; if (!getGlobalVisibleRect(rect)) { return false; } if (mTempMatrix == null) { mTempMatrix = new Matrix(); } Matrix matrix = mTempMatrix; matrix.reset(); transformMatrixToLocal(matrix); editorBounds.set(rect); // When the view has transformations like scaleX/scaleY computing the global visible // rectangle will already apply the transformations. The getLocalVisibleRect only offsets // the global rectangle to local. And the result is wrong the View is scaled. // // This approach use the local transformation matrix to map the global rectangle to // local instead. // // Note: it doesn't work well with rotation. Because Rect must be // axis-aligned, when a rotated Rect becomes quadrilateral, the quadrilateral's // bounding box is stored at Rect instead. It makes the returned Rect larger than // the correct size. matrix.mapRect(editorBounds); if (handwritingBounds != null) { // Similar to editorBounds, handwritingBounds must be computed in global coordinates // and then converted back to local coordinates. Otherwise, if the view is scaled, // the handwritingBoundsOffsets are also scaled, which is not the expected behavior. handwritingBounds.top = rect.top - getHandwritingBoundsOffsetTop(); handwritingBounds.left = rect.left - getHandwritingBoundsOffsetLeft(); handwritingBounds.bottom = rect.bottom + getHandwritingBoundsOffsetBottom(); handwritingBounds.right = rect.right + getHandwritingBoundsOffsetRight(); matrix.mapRect(handwritingBounds); } return true; } private boolean getContentVisibleRect(RectF rect) { if (!getEditorAndHandwritingBounds(rect, /* handwritingBounds= */null)) { return false; } // Clip the view's visible rect with the text layout's visible rect. return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(), getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom()); } /** * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} * Loading @@ -14333,9 +14423,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // character bounds in this case yet. return; } final RectF visibleRect = new RectF(); if (Flags.handwritingGestureWithTransformation()) { getContentVisibleRect(visibleRect); } else { final Rect rect = new Rect(); getContentVisibleRect(rect); final RectF visibleRect = new RectF(rect); visibleRect.set(rect); } final float[] characterBounds = getCharacterBounds(startIndex, endIndex, viewportToContentHorizontalOffset, viewportToContentVerticalOffset); Loading Loading @@ -14438,24 +14534,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener builder.setMatrix(viewToScreenMatrix); if (includeEditorBounds) { final RectF editorBounds = new RectF(); final RectF handwritingBounds = new RectF(); if (Flags.handwritingGestureWithTransformation()) { getEditorAndHandwritingBounds(editorBounds, handwritingBounds); } else { if (mTempRect == null) { mTempRect = new Rect(); } final Rect bounds = mTempRect; final RectF editorBounds; final RectF handwritingBounds; // If the editor is not visible at all, return empty rectangles. We still need to // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo. if (getViewVisibleRect(bounds)) { editorBounds = new RectF(bounds); handwritingBounds = new RectF(editorBounds); editorBounds.set(bounds); handwritingBounds.set(editorBounds); handwritingBounds.top -= getHandwritingBoundsOffsetTop(); handwritingBounds.left -= getHandwritingBoundsOffsetLeft(); handwritingBounds.bottom += getHandwritingBoundsOffsetBottom(); handwritingBounds.right += getHandwritingBoundsOffsetRight(); } else { // The editor is not visible at all, return empty rectangles. We still need to // return an EditorBoundsInfo because IME has subscribed the EditorBoundsInfo. editorBounds = new RectF(); handwritingBounds = new RectF(); } } EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) Loading Loading @@ -14533,6 +14631,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (includeVisibleLineBounds) { if (Flags.handwritingGestureWithTransformation()) { RectF visibleRect = new RectF(); if (getContentVisibleRect(visibleRect)) { // Subtract the viewportToContentVerticalOffset to convert the view // coordinates to layout coordinates. final float visibleTop = visibleRect.top - viewportToContentVerticalOffset; final float visibleBottom = visibleRect.bottom - viewportToContentVerticalOffset; final int firstLine = layout.getLineForVertical((int) Math.floor(visibleTop)); final int lastLine = layout.getLineForVertical((int) Math.ceil(visibleBottom)); for (int line = firstLine; line <= lastLine; ++line) { final float left = layout.getLineLeft(line) + viewportToContentHorizontalOffset; final float top = layout.getLineTop(line) + viewportToContentVerticalOffset; final float right = layout.getLineRight(line) + viewportToContentHorizontalOffset; final float bottom = layout.getLineBottom(line, false) + viewportToContentVerticalOffset; builder.addVisibleLineBounds(left, top, right, bottom); } } } else { final Rect visibleRect = new Rect(); if (getContentVisibleRect(visibleRect)) { // Subtract the viewportToContentVerticalOffset to convert the view Loading Loading @@ -14560,6 +14685,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } } if (includeTextAppearance) { builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this));