Merge "Fix that handwriting gesture not working if View is scaled" into main
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 1b85191..bb3f6c9 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -266,4 +266,14 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
-}
\ No newline at end of file
+}
+
+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
+  }
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ac899f4..61ecc62 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,10 +28,10 @@
 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;
@@ -937,6 +937,7 @@
     private TextPaint mTempTextPaint;
 
     private Object mTempCursor;
+    private Matrix mTempMatrix;
 
     @UnsupportedAppUsage
     private BoringLayout.Metrics mBoring;
@@ -12106,6 +12107,22 @@
     }
 
     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(
@@ -12115,6 +12132,22 @@
     }
 
     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(
@@ -14279,6 +14312,9 @@
     }
 
     /**
+     * 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.
@@ -14299,6 +14335,8 @@
     }
 
     /**
+     * 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.
      *
@@ -14314,6 +14352,58 @@
                 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}
      *
@@ -14333,9 +14423,15 @@
             // character bounds in this case yet.
             return;
         }
-        final Rect rect = new Rect();
-        getContentVisibleRect(rect);
-        final RectF visibleRect = new RectF(rect);
+        final RectF visibleRect = new RectF();
+
+        if (Flags.handwritingGestureWithTransformation()) {
+            getContentVisibleRect(visibleRect);
+        } else {
+            final Rect rect = new Rect();
+            getContentVisibleRect(rect);
+            visibleRect.set(rect);
+        }
 
         final float[] characterBounds = getCharacterBounds(startIndex, endIndex,
                 viewportToContentHorizontalOffset, viewportToContentVerticalOffset);
@@ -14438,24 +14534,26 @@
         builder.setMatrix(viewToScreenMatrix);
 
         if (includeEditorBounds) {
-            if (mTempRect == null) {
-                mTempRect = new Rect();
-            }
-            final Rect bounds = mTempRect;
-            final RectF editorBounds;
-            final RectF handwritingBounds;
-            if (getViewVisibleRect(bounds)) {
-                editorBounds = new RectF(bounds);
-                handwritingBounds = new RectF(editorBounds);
-                handwritingBounds.top -= getHandwritingBoundsOffsetTop();
-                handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
-                handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
-                handwritingBounds.right += getHandwritingBoundsOffsetRight();
+            final RectF editorBounds = new RectF();
+            final RectF handwritingBounds = new RectF();
+            if (Flags.handwritingGestureWithTransformation()) {
+                getEditorAndHandwritingBounds(editorBounds, handwritingBounds);
             } else {
-                // The editor is not visible at all, return empty rectangles. We still need to
+                if (mTempRect == null) {
+                    mTempRect = new Rect();
+                }
+                final Rect bounds = mTempRect;
+
+                // If 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();
+                if (getViewVisibleRect(bounds)) {
+                    editorBounds.set(bounds);
+                    handwritingBounds.set(editorBounds);
+                    handwritingBounds.top -= getHandwritingBoundsOffsetTop();
+                    handwritingBounds.left -= getHandwritingBoundsOffsetLeft();
+                    handwritingBounds.bottom += getHandwritingBoundsOffsetBottom();
+                    handwritingBounds.right += getHandwritingBoundsOffsetRight();
+                }
             }
             EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
             EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
@@ -14533,29 +14631,57 @@
             }
 
             if (includeVisibleLineBounds) {
-                final Rect visibleRect = new Rect();
-                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));
+                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);
+                        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
+                        // 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);
+                        }
                     }
                 }
             }