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

Commit 9a67e3ca authored by Haoyu Zhang's avatar Haoyu Zhang
Browse files

Fix that handwriting gesture not working if View is scaled

Bug: 342619429
Test: atest TextViewWithTransformationHandwritingTest
Flag: com.android.text.flags.handwriting_gesture_with_transformation

Change-Id: I3230327ab623cdc369c7f2353cec10bc30688ff8
parent 5d157cee
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -260,3 +260,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
  }
}
+169 −43
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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(
@@ -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(
@@ -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.
@@ -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.
     *
@@ -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}
     *
@@ -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);
@@ -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)
@@ -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
@@ -14560,6 +14685,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                    }
                }
            }
        }
        if (includeTextAppearance) {
            builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this));