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

Commit 9e0af1b8 authored by Haoyu Zhang's avatar Haoyu Zhang Committed by Automerger Merge Worker
Browse files

Merge "Fix: CursorAnchorInfo EditorBoundsInfo includes non-visible region"...

Merge "Fix: CursorAnchorInfo EditorBoundsInfo includes non-visible region" into udc-qpr-dev am: 7490d9e8

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23755340



Change-Id: Iaf49029c829641e4f766117584944237797becda
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents dac2d6a5 7490d9e8
Loading
Loading
Loading
Loading
+37 −11
Original line number Diff line number Diff line
@@ -13827,13 +13827,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    }
    /**
     * Helper method to set {@code rect} to the text content's non-clipped area in the view's
     * coordinates.
     * 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.
     *
     * @return true if at least part of the text content is visible; false if the text content is
     * completely clipped or translated out of the visible area.
     */
    private boolean getContentVisibleRect(Rect rect) {
    private boolean getViewVisibleRect(Rect rect) {
        if (!getLocalVisibleRect(rect)) {
            return false;
        }
@@ -13842,6 +13843,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        // view's coordinates. So we need to offset it with the negative scrolled amount to convert
        // it to view's coordinate.
        rect.offset(-getScrollX(), -getScrollY());
        return true;
    }
    /**
     * Helper method to set {@code rect} to the text content's non-clipped area in the view's
     * coordinates.
     *
     * @return true if at least part of the text content is visible; false if the text content is
     * completely clipped or translated out of the visible area.
     */
    private boolean getContentVisibleRect(Rect rect) {
        if (!getViewVisibleRect(rect)) {
            return false;
        }
        // Clip the view's visible rect with the text layout's visible rect.
        return rect.intersect(getCompoundPaddingLeft(), getCompoundPaddingTop(),
                getWidth() - getCompoundPaddingRight(), getHeight() - getCompoundPaddingBottom());
@@ -13971,14 +13986,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        builder.setMatrix(viewToScreenMatrix);
        if (includeEditorBounds) {
            final RectF editorBounds = new RectF();
            editorBounds.set(0 /* left */, 0 /* top */,
                    getWidth(), getHeight());
            final RectF handwritingBounds = new RectF(
                    -getHandwritingBoundsOffsetLeft(),
                    -getHandwritingBoundsOffsetTop(),
                    getWidth() + getHandwritingBoundsOffsetRight(),
                    getHeight() + getHandwritingBoundsOffsetBottom());
            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();
            } 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)
                    .setHandwritingBounds(handwritingBounds).build();
+78 −20
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorBoundsInfo;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -54,8 +55,15 @@ public class EditTextCursorAnchorInfoTest {
    private static final int[] sLocationOnScreen = new int[2];
    private static Typeface sTypeface;
    private static final float TEXT_SIZE = 1f;
    // The line height of the test font font is 1.2 * textSize.
    // The line height of the test font is 1.2 * textSize.
    private static final int LINE_HEIGHT = 12;
    private static final int HW_BOUNDS_OFFSET_LEFT = 10;
    private static final int HW_BOUNDS_OFFSET_TOP = 20;
    private static final int HW_BOUNDS_OFFSET_RIGHT = 30;
    private static final int HW_BOUNDS_OFFSET_BOTTOM = 40;


    // Default text has 5 lines of text. The needed width is 50px and the needed height is 60px.
    private static final CharSequence DEFAULT_TEXT = "X\nXX\nXXX\nXXXX\nXXXXX";
    private static final ImmutableList<RectF> DEFAULT_LINE_BOUNDS = ImmutableList.of(
            new RectF(0f, 0f, 10f, LINE_HEIGHT),
@@ -130,6 +138,55 @@ public class EditTextCursorAnchorInfoTest {
        assertThat(actualMatrix).isEqualTo(expectedMatrix);
    }

    @Test
    public void testEditorBoundsInfo_allVisible() {
        // The needed width and height of the DEFAULT_TEXT are 50 px and 60 px respectfully.
        int width = 100;
        int height = 200;
        setupEditText(DEFAULT_TEXT, width, height);
        CursorAnchorInfo cursorAnchorInfo =
                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
        assertThat(editorBoundsInfo).isNotNull();
        assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, width, height));
        assertThat(editorBoundsInfo.getHandwritingBounds())
                .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, -HW_BOUNDS_OFFSET_TOP,
                        width + HW_BOUNDS_OFFSET_RIGHT, height + HW_BOUNDS_OFFSET_BOTTOM));
    }

    @Test
    public void testEditorBoundsInfo_scrolled() {
        // The height of the editor will be 60 px.
        int width = 100;
        int visibleTop = 10;
        int visibleBottom = 30;
        setupVerticalClippedEditText(width, visibleTop, visibleBottom);
        CursorAnchorInfo cursorAnchorInfo =
                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
        assertThat(editorBoundsInfo).isNotNull();
        assertThat(editorBoundsInfo.getEditorBounds())
                .isEqualTo(new RectF(0, visibleTop, width, visibleBottom));
        assertThat(editorBoundsInfo.getHandwritingBounds())
                .isEqualTo(new RectF(-HW_BOUNDS_OFFSET_LEFT, visibleTop - HW_BOUNDS_OFFSET_TOP,
                        width + HW_BOUNDS_OFFSET_RIGHT, visibleBottom + HW_BOUNDS_OFFSET_BOTTOM));
    }

    @Test
    public void testEditorBoundsInfo_invisible() {
        // The height of the editor will be 60px. Scroll it to 70px will make it invisible.
        int width = 100;
        int visibleTop = 70;
        int visibleBottom = 70;
        setupVerticalClippedEditText(width, visibleTop, visibleBottom);
        CursorAnchorInfo cursorAnchorInfo =
                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
        EditorBoundsInfo editorBoundsInfo = cursorAnchorInfo.getEditorBoundsInfo();
        assertThat(editorBoundsInfo).isNotNull();
        assertThat(editorBoundsInfo.getEditorBounds()).isEqualTo(new RectF(0, 0, 0, 0));
        assertThat(editorBoundsInfo.getHandwritingBounds()).isEqualTo(new RectF(0, 0, 0, 0));
    }

    @Test
    public void testVisibleLineBounds_allVisible() {
        setupEditText(DEFAULT_TEXT, /* height= */ 100);
@@ -465,32 +522,26 @@ public class EditTextCursorAnchorInfoTest {
    }

    private void setupVerticalClippedEditText(int visibleTop, int visibleBottom) {
        ScrollView scrollView = new ScrollView(mActivity);
        mEditText = new EditText(mActivity);
        mEditText.setTypeface(sTypeface);
        mEditText.setText(DEFAULT_TEXT);
        mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);

        mEditText.setPadding(0, 0, 0, 0);
        mEditText.setCompoundDrawables(null, null, null, null);
        mEditText.setCompoundDrawablePadding(0);

        mEditText.scrollTo(0, 0);
        mEditText.setLineSpacing(0f, 1f);
        setupVerticalClippedEditText(1000, visibleTop, visibleBottom);
    }

        // Place the text layout top to the view's top.
        mEditText.setGravity(Gravity.TOP);
        int width = 1000;
        int height = visibleBottom - visibleTop;
    /**
     * Helper method to create an EditText in a vertical ScrollView so that its visible bounds
     * is Rect(0, visibleTop, width, visibleBottom) in the EditText's coordinates. Both ScrollView
     * and EditText's width is set to the given width.
     */
    private void setupVerticalClippedEditText(int width, int visibleTop, int visibleBottom) {
        ScrollView scrollView = new ScrollView(mActivity);
        createEditText();
        int scrollViewHeight = visibleBottom - visibleTop;

        scrollView.addView(mEditText, new FrameLayout.LayoutParams(
                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(5 * LINE_HEIGHT, View.MeasureSpec.EXACTLY)));
        scrollView.measure(
                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
        scrollView.layout(0, 0, width, height);

                View.MeasureSpec.makeMeasureSpec(scrollViewHeight, View.MeasureSpec.EXACTLY));
        scrollView.layout(0, 0, width, scrollViewHeight);
        scrollView.scrollTo(0, visibleTop);
    }

@@ -499,6 +550,11 @@ public class EditTextCursorAnchorInfoTest {
        measureEditText(height);
    }

    private void setupEditText(CharSequence text, int width, int height) {
        createEditText(text);
        measureEditText(width, height);
    }

    private void setupEditText(CharSequence text, int height, float lineSpacing,
            float lineMultiplier) {
        createEditText(text);
@@ -537,6 +593,8 @@ public class EditTextCursorAnchorInfoTest {
        mEditText.setTypeface(sTypeface);
        mEditText.setText(text);
        mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
        mEditText.setHandwritingBoundsOffsets(HW_BOUNDS_OFFSET_LEFT, HW_BOUNDS_OFFSET_TOP,
                HW_BOUNDS_OFFSET_RIGHT, HW_BOUNDS_OFFSET_BOTTOM);

        mEditText.setPadding(0, 0, 0, 0);
        mEditText.setCompoundDrawables(null, null, null, null);