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

Commit f4aa7d2e authored by Siyamed Sinir's avatar Siyamed Sinir Committed by Android (Google) Code Review
Browse files

Merge "Clamp EditText cursor in the drawable boundaries." into nyc-dev

parents c3153986 217c0f71
Loading
Loading
Loading
Loading
+71 −12
Original line number Diff line number Diff line
@@ -1851,8 +1851,7 @@ public class Editor {
        updateCursorPosition(0, top, middle, layout.getPrimaryHorizontal(offset, clamped));

        if (mCursorCount == 2) {
            updateCursorPosition(1, middle, bottom,
                    layout.getSecondaryHorizontal(offset, clamped));
            updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset, clamped));
        }
    }

@@ -2151,18 +2150,57 @@ public class Editor {
        return mSelectionModifierCursorController;
    }

    /**
     * @hide
     */
    @VisibleForTesting
    public Drawable[] getCursorDrawable() {
        return mCursorDrawable;
    }

    private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
        if (mCursorDrawable[cursorIndex] == null)
            mCursorDrawable[cursorIndex] = mTextView.getContext().getDrawable(
                    mTextView.mCursorDrawableRes);
        final Drawable drawable = mCursorDrawable[cursorIndex];
        final int left = clampCursorHorizontalPosition(drawable, horizontal);
        final int width = drawable.getIntrinsicWidth();
        drawable.setBounds(left, top - mTempRect.top, left + width,
                bottom + mTempRect.bottom);
    }

        if (mTempRect == null) mTempRect = new Rect();
        mCursorDrawable[cursorIndex].getPadding(mTempRect);
        final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
    /**
     * Return clamped position for the cursor. If the cursor is within the boundaries of the view,
     * then it is offset with the left padding of the cursor drawable. If the cursor is at
     * the beginning or the end of the text then its drawable edge is aligned with left or right of
     * the view boundary.
     *
     * @param drawable   Cursor drawable.
     * @param horizontal Horizontal position for the cursor.
     * @return The clamped horizontal position for the cursor.
     */
    private final int clampCursorHorizontalPosition(final Drawable drawable, float
            horizontal) {
        horizontal = Math.max(0.5f, horizontal - 0.5f);
        final int left = (int) (horizontal) - mTempRect.left;
        mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
                bottom + mTempRect.bottom);
        if (mTempRect == null) mTempRect = new Rect();
        drawable.getPadding(mTempRect);
        int scrollX = mTextView.getScrollX();
        float horizontalDiff = horizontal - scrollX;
        int viewClippedWidth = mTextView.getWidth() - mTextView.getCompoundPaddingLeft()
                - mTextView.getCompoundPaddingRight();

        final int left;
        if (horizontalDiff >= (viewClippedWidth - 1f)) {
            // at the rightmost position
            final int cursorWidth = drawable.getIntrinsicWidth();
            left = viewClippedWidth + scrollX - (cursorWidth - mTempRect.right);
        } else if (Math.abs(horizontalDiff) <= 1f) {
            // at the leftmost position
            left = scrollX - mTempRect.left;
        } else {
            left = (int) horizontal - mTempRect.left;
        }
        return left;
    }

    /**
@@ -3919,8 +3957,8 @@ public class Editor {
            final Layout layout = mTextView.getLayout();
            if (layout != null && oldDrawable != mDrawable && isShowing()) {
                // Update popup window position.
                mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX -
                        getHorizontalOffset() + getCursorOffset());
                mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX -
                        getHorizontalOffset() + getCursorOffset();
                mPositionX += mTextView.viewportToContentHorizontalOffset();
                mPositionHasChanged = true;
                updatePosition(mLastParentX, mLastParentY, false, false);
@@ -4049,8 +4087,8 @@ public class Editor {
                final int line = layout.getLineForOffset(offset);
                mPrevLine = line;

                mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX -
                        getHorizontalOffset() + getCursorOffset());
                mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX -
                        getHorizontalOffset() + getCursorOffset();
                mPositionY = layout.getLineBottom(line);

                // Take TextView's padding and scroll into account.
@@ -4062,6 +4100,17 @@ public class Editor {
            }
        }

        /**
         * Return the clamped horizontal position for the first cursor.
         *
         * @param layout Text layout.
         * @param offset Character offset for the cursor.
         * @return The clamped horizontal position for the cursor.
         */
        int getCursorHorizontalPosition(Layout layout, int offset) {
            return (int) (layout.getPrimaryHorizontal(offset) - 0.5f);
        }

        public void updatePosition(int parentPositionX, int parentPositionY,
                boolean parentPositionChanged, boolean parentScrolled) {
            positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled);
@@ -4299,6 +4348,16 @@ public class Editor {
            return offset;
        }

        @Override
        int getCursorHorizontalPosition(Layout layout, int offset) {
            final Drawable drawable = mCursorCount > 0 ? mCursorDrawable[0] : null;
            if (drawable != null) {
                final float horizontal = layout.getPrimaryHorizontal(offset);
                return clampCursorHorizontalPosition(drawable, horizontal) + mTempRect.left;
            }
            return super.getCursorHorizontalPosition(layout, offset);
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            final boolean result = super.onTouchEvent(ev);
+2 −8
Original line number Diff line number Diff line
@@ -5460,15 +5460,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return (int) Math.max(0, mShadowDy + mShadowRadius);
    }

    private int getFudgedPaddingRight() {
        // Add sufficient space for cursor and tone marks
        int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
        return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1));
    }

    @Override
    protected int getRightPaddingOffset() {
        return -(getFudgedPaddingRight() - mPaddingRight) +
        return -(getCompoundPaddingRight() - mPaddingRight) +
                (int) Math.max(0, mShadowDx + mShadowRadius);
    }

@@ -5813,7 +5807,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

        float clipLeft = compoundPaddingLeft + scrollX;
        float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
        float clipRight = right - left - getFudgedPaddingRight() + scrollX;
        float clipRight = right - left - getCompoundPaddingRight() + scrollX;
        float clipBottom = bottom - top + scrollY -
                ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);

+164 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.ViewGroup;

public class EditorCursorTest extends ActivityInstrumentationTestCase2<TextViewActivity> {

    private EditText mEditText;
    private final String RTL_STRING = "مرحبا الروبوت مرحبا الروبوت مرحبا الروبوت";

    public EditorCursorTest() {
        super(TextViewActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mEditText = new EditText(getActivity());
        mEditText.setTextSize(30);
        mEditText.setSingleLine(true);
        mEditText.setLines(1);
        mEditText.setPadding(15, 15, 15, 15);
        ViewGroup.LayoutParams editTextLayoutParams = new ViewGroup.LayoutParams(200,
                ViewGroup.LayoutParams.WRAP_CONTENT);

        mEditText.setLayoutParams(editTextLayoutParams);

        final FrameLayout layout = new FrameLayout(getActivity());
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT);
        layout.setLayoutParams(layoutParams);
        layout.addView(mEditText);

        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                getActivity().setContentView(layout);
                mEditText.requestFocus();
            }
        });
        getInstrumentation().waitForIdleSync();
    }

    @SmallTest
    public void testCursorIsInViewBoundariesWhenOnRightForLtr() throws Exception {
        // Asserts that when an EditText has LTR text, and cursor is at the end (right),
        // cursor is drawn to the right edge of the view
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mEditText.setText("aaaaaaaaaaaaaaaaaaaaaa");
                int length = mEditText.getText().length();
                mEditText.setSelection(length, length);
            }
        });
        getInstrumentation().waitForIdleSync();

        Editor editor = mEditText.getEditorForTesting();
        Drawable drawable = editor.getCursorDrawable()[0];
        Rect drawableBounds = drawable.getBounds();
        Rect drawablePadding = new Rect();
        drawable.getPadding(drawablePadding);

        // right edge of the view including the scroll
        int maxRight = mEditText.getWidth() - mEditText.getCompoundPaddingRight()
                - mEditText.getCompoundPaddingLeft() + +mEditText.getScrollX();
        int diff = drawableBounds.right - drawablePadding.right - maxRight;
        assertTrue(diff >= 0 && diff <= 1);
    }

    @SmallTest
    public void testCursorIsInViewBoundariesWhenOnLeftForLtr() throws Exception {
        // Asserts that when an EditText has LTR text, and cursor is at the beginning,
        // cursor is drawn to the left edge of the view

        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mEditText.setText("aaaaaaaaaaaaaaaaaaaaaa");
                mEditText.setSelection(0, 0);
            }
        });
        getInstrumentation().waitForIdleSync();

        Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0];
        Rect drawableBounds = drawable.getBounds();
        Rect drawablePadding = new Rect();
        drawable.getPadding(drawablePadding);

        int diff = drawableBounds.left + drawablePadding.left;
        assertTrue(diff >= 0 && diff <= 1);
    }

    @SmallTest
    public void testCursorIsInViewBoundariesWhenOnRightForRtl() throws Exception {
        // Asserts that when an EditText has RTL text, and cursor is at the end,
        // cursor is drawn to the left edge of the view

        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mEditText.setText(RTL_STRING);
                mEditText.setSelection(0, 0);
            }
        });
        getInstrumentation().waitForIdleSync();

        Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0];
        Rect drawableBounds = drawable.getBounds();
        Rect drawablePadding = new Rect();
        drawable.getPadding(drawablePadding);

        int maxRight = mEditText.getWidth() - mEditText.getCompoundPaddingRight()
                - mEditText.getCompoundPaddingLeft() + mEditText.getScrollX();

        int diff = drawableBounds.right - drawablePadding.right - maxRight;
        assertTrue(diff >= 0 && diff <= 1);
    }

    @SmallTest
    public void testCursorIsInViewBoundariesWhenOnLeftForRtl() throws Exception {
        // Asserts that when an EditText has RTL text, and cursor is at the beginning,
        // cursor is drawn to the right edge of the view

        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mEditText.setText(RTL_STRING);
                int length = mEditText.getText().length();
                mEditText.setSelection(length, length);
            }
        });
        getInstrumentation().waitForIdleSync();

        Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0];
        Rect drawableBounds = drawable.getBounds();
        Rect drawablePadding = new Rect();
        drawable.getPadding(drawablePadding);

        int diff = drawableBounds.left - mEditText.getScrollX() + drawablePadding.left;
        assertTrue(diff >= 0 && diff <= 1);
    }

}