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);
+ }
}
}
}