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

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

Merge "Do not use hint text for Editor cursor positioning" into nyc-dev

parents 125db3f9 987ec658
Loading
Loading
Loading
Loading
+29 −32
Original line number Diff line number Diff line
@@ -1827,7 +1827,7 @@ public class Editor {
            return;
        }

        Layout layout = getActiveLayout();
        Layout layout = mTextView.getLayout();
        final int offset = mTextView.getSelectionStart();
        final int line = layout.getLineForOffset(offset);
        final int top = layout.getLineTop(line);
@@ -2192,27 +2192,35 @@ public class Editor {
            mCursorDrawable[cursorIndex] = mTextView.getContext().getDrawable(
                    mTextView.mCursorDrawableRes);
        final Drawable drawable = mCursorDrawable[cursorIndex];
        final int left = clampCursorHorizontalPosition(drawable, horizontal);
        final int left = clampHorizontalPosition(drawable, horizontal);
        final int width = drawable.getIntrinsicWidth();
        drawable.setBounds(left, top - mTempRect.top, left + width,
                bottom + mTempRect.bottom);
    }

    /**
     * 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
     * Return clamped position for the drawable. If the drawable is within the boundaries of the
     * view, then it is offset with the left padding of the cursor drawable. If the drawable is at
     * the beginning or the end of the text then its drawable edge is aligned with left or right of
     * the view boundary.
     * the view boundary. If the drawable is null, horizontal parameter is aligned to left or right
     * of the view.
     *
     * @param drawable   Cursor drawable.
     * @param horizontal Horizontal position for the cursor.
     * @return The clamped horizontal position for the cursor.
     * @param drawable Drawable. Can be null.
     * @param horizontal Horizontal position for the drawable.
     * @return The clamped horizontal position for the drawable.
     */
    private final int clampCursorHorizontalPosition(final Drawable drawable, float
            horizontal) {
    private int clampHorizontalPosition(@Nullable final Drawable drawable, float horizontal) {
        horizontal = Math.max(0.5f, horizontal - 0.5f);
        if (mTempRect == null) mTempRect = new Rect();

        int drawableWidth = 0;
        if (drawable != null) {
            drawable.getPadding(mTempRect);
            drawableWidth = drawable.getIntrinsicWidth();
        } else {
            mTempRect.setEmpty();
        }

        int scrollX = mTextView.getScrollX();
        float horizontalDiff = horizontal - scrollX;
        int viewClippedWidth = mTextView.getWidth() - mTextView.getCompoundPaddingLeft()
@@ -2221,9 +2229,11 @@ public class Editor {
        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) {
            left = viewClippedWidth + scrollX - (drawableWidth - mTempRect.right);
        } else if (Math.abs(horizontalDiff) <= 1f ||
                (TextUtils.isEmpty(mTextView.getText())
                        && (TextView.VERY_WIDE - scrollX) <= (viewClippedWidth + 1f)
                        && horizontal <= 1f)) {
            // at the leftmost position
            left = scrollX - mTempRect.left;
        } else {
@@ -3772,10 +3782,10 @@ public class Editor {
                                + mHandleHeight);
            } else {
                // We have a single cursor.
                Layout layout = getActiveLayout();
                Layout layout = mTextView.getLayout();
                int line = layout.getLineForOffset(mTextView.getSelectionStart());
                float primaryHorizontal =
                        layout.getPrimaryHorizontal(mTextView.getSelectionStart());
                float primaryHorizontal = clampHorizontalPosition(null,
                        layout.getPrimaryHorizontal(mTextView.getSelectionStart()));
                mSelectionBounds.set(
                        primaryHorizontal,
                        layout.getLineTop(line),
@@ -4152,7 +4162,7 @@ public class Editor {
                prepareCursorControllers();
                return;
            }
            layout = getActiveLayout();
            layout = mTextView.getLayout();

            boolean offsetChanged = offset != mPreviousOffset;
            if (offsetChanged || parentScrolled) {
@@ -4322,19 +4332,6 @@ public class Editor {
        public void onDetached() {}
    }

    /**
     * Returns the active layout (hint or text layout). Note that the text layout can be null.
     */
    private Layout getActiveLayout() {
        Layout layout = mTextView.getLayout();
        Layout hintLayout = mTextView.getHintLayout();
        if (TextUtils.isEmpty(layout.getText()) && hintLayout != null &&
                !TextUtils.isEmpty(hintLayout.getText())) {
            layout = hintLayout;
        }
        return layout;
    }

    private class InsertionHandleView extends HandleView {
        private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
        private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
@@ -4431,7 +4428,7 @@ public class Editor {
            final Drawable drawable = mCursorCount > 0 ? mCursorDrawable[0] : null;
            if (drawable != null) {
                final float horizontal = layout.getPrimaryHorizontal(offset);
                return clampCursorHorizontalPosition(drawable, horizontal) + mTempRect.left;
                return clampHorizontalPosition(drawable, horizontal) + mTempRect.left;
            }
            return super.getCursorHorizontalPosition(layout, offset);
        }
+2 −2
Original line number Diff line number Diff line
@@ -285,8 +285,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

    private static final RectF TEMP_RECTF = new RectF();

    // XXX should be much larger
    private static final int VERY_WIDE = 1024*1024;
    /** @hide */
    static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger
    private static final int ANIMATED_SCROLL_GAP = 250;

    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+1 −1
Original line number Diff line number Diff line
@@ -111,7 +111,7 @@
    <!-- accessibility test permissions -->
    <uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" />

    <application android:theme="@style/Theme">
    <application android:theme="@style/Theme" android:supportsRtl="true">
        <uses-library android:name="android.test.runner" />
        <uses-library android:name="org.apache.http.legacy" android:required="false" />
        <meta-data
+139 −71
Original line number Diff line number Diff line
@@ -16,16 +16,34 @@

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.Choreographer;
import android.view.ViewGroup;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerOnLeft;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerOnRight;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;

public class EditorCursorTest extends ActivityInstrumentationTestCase2<TextViewActivity> {


    private final static String LTR_STRING = "aaaaaaaaaaaaaaaaaaaaaa";
    private final static String LTR_HINT = "hint";
    private final static String RTL_STRING = "مرحبا الروبوت مرحبا الروبوت مرحبا الروبوت";
    private final static String RTL_HINT = "الروبوت";
    private final static int CURSOR_BLINK_MS = 500;

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

    public EditorCursorTest() {
        super(TextViewActivity.class);
@@ -55,110 +73,160 @@ public class EditorCursorTest extends ActivityInstrumentationTestCase2<TextViewA
            @Override
            public void run() {
                getActivity().setContentView(layout);
                mEditText.requestFocus();
            }
        });
        getInstrumentation().waitForIdleSync();
        onView(sameInstance(mEditText)).perform(click());
    }

    @SmallTest
    public void testCursorIsInViewBoundariesWhenOnRightForLtr() throws Exception {
    public void testCursorIsInViewBoundariesWhenOnRightForLtr() {
        // 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);
        setEditTextText(LTR_STRING, LTR_STRING.length());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnRight());
    }
        });
        getInstrumentation().waitForIdleSync();

        Editor editor = mEditText.getEditorForTesting();
        Drawable drawable = editor.getCursorDrawable()[0];
        Rect drawableBounds = drawable.getBounds();
        Rect drawablePadding = new Rect();
        drawable.getPadding(drawablePadding);
    @SmallTest
    public void testCursorIsInViewBoundariesWhenOnLeftForLtr() {
        // Asserts that when an EditText has LTR text, and cursor is at the beginning,
        // cursor is drawn to the left edge of the view
        setEditTextText(LTR_STRING, 0);

        // 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);
        onView(sameInstance(mEditText)).check(hasInsertionPointerOnLeft());
    }

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

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

        Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0];
        Rect drawableBounds = drawable.getBounds();
        Rect drawablePadding = new Rect();
        drawable.getPadding(drawablePadding);
    @SmallTest
    public void testCursorIsInViewBoundariesWhenOnLeftForRtl() {
        // Asserts that when an EditText has RTL text, and cursor is at the beginning,
        // cursor is drawn to the right edge of the view
        setEditTextText(RTL_STRING, RTL_STRING.length());

        int diff = drawableBounds.left + drawablePadding.left;
        assertTrue(diff >= 0 && diff <= 1);
        onView(sameInstance(mEditText)).check(hasInsertionPointerOnLeft());
    }

    /* Tests for cursor positioning with hint */
    @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
    public void testCursorIsOnLeft_withFirstStrongLtrAlgorithm() {
        setEditTextHint(null, TextView.TEXT_DIRECTION_FIRST_STRONG_LTR, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());
        assertThat(mEditText.getHint(), nullValue());

        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mEditText.setText(RTL_STRING);
                mEditText.setSelection(0, 0);
        onView(sameInstance(mEditText)).check(hasInsertionPointerOnLeft());

        setEditTextHint(RTL_HINT, TextView.TEXT_DIRECTION_FIRST_STRONG_LTR, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnLeft());

        setEditTextHint(LTR_HINT, TextView.TEXT_DIRECTION_FIRST_STRONG_LTR, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnLeft());
    }
        });
        getInstrumentation().waitForIdleSync();

        Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0];
        Rect drawableBounds = drawable.getBounds();
        Rect drawablePadding = new Rect();
        drawable.getPadding(drawablePadding);
    @SmallTest
    public void testCursorIsOnRight_withFirstStrongRtlAlgorithm() {
        setEditTextHint(null, TextView.TEXT_DIRECTION_FIRST_STRONG_RTL, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());
        assertThat(mEditText.getHint(), nullValue());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnRight());

        setEditTextHint(LTR_HINT, TextView.TEXT_DIRECTION_FIRST_STRONG_RTL, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnRight());

        int maxRight = mEditText.getWidth() - mEditText.getCompoundPaddingRight()
                - mEditText.getCompoundPaddingLeft() + mEditText.getScrollX();
        setEditTextHint(RTL_HINT, TextView.TEXT_DIRECTION_FIRST_STRONG_RTL, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());

        int diff = drawableBounds.right - drawablePadding.right - maxRight;
        assertTrue(diff >= 0 && diff <= 1);
        onView(sameInstance(mEditText)).check(hasInsertionPointerOnRight());
    }

    @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
    public void testCursorIsOnLeft_withLtrAlgorithm() {
        setEditTextHint(null, TextView.TEXT_DIRECTION_LTR, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());
        assertThat(mEditText.getHint(), nullValue());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnLeft());

        setEditTextHint(RTL_HINT, TextView.TEXT_DIRECTION_LTR, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnLeft());

        setEditTextHint(LTR_HINT, TextView.TEXT_DIRECTION_LTR, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnLeft());
    }

    @SmallTest
    public void testCursorIsOnRight_withRtlAlgorithm() {
        setEditTextHint(null, TextView.TEXT_DIRECTION_RTL, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());
        assertThat(mEditText.getHint(), nullValue());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnRight());

        setEditTextHint(LTR_HINT, TextView.TEXT_DIRECTION_RTL, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnRight());

        setEditTextHint(RTL_HINT, TextView.TEXT_DIRECTION_RTL, 0);
        assertThat(mEditText.getText().toString(), isEmptyString());

        onView(sameInstance(mEditText)).check(hasInsertionPointerOnRight());
    }

    private void setEditTextProperties(final String text, final String hint,
            final Integer textDirection, final Integer selection) {
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mEditText.setText(RTL_STRING);
                int length = mEditText.getText().length();
                mEditText.setSelection(length, length);
                if (textDirection != null) mEditText.setTextDirection(textDirection);
                if (text != null) mEditText.setText(text);
                if (hint != null) mEditText.setHint(hint);
                if (selection != null) mEditText.setSelection(selection);
            }
        });
        getInstrumentation().waitForIdleSync();

        Drawable drawable = mEditText.getEditorForTesting().getCursorDrawable()[0];
        Rect drawableBounds = drawable.getBounds();
        Rect drawablePadding = new Rect();
        drawable.getPadding(drawablePadding);
        // wait for cursor to be drawn. updateCursorPositions function is called during draw() and
        // only when cursor is visible during blink.
        final CountDownLatch latch = new CountDownLatch(1);
        mEditText.postOnAnimationDelayed(new Runnable() {
            @Override
            public void run() {
                latch.countDown();
            }
        }, CURSOR_BLINK_MS);
        try {
            assertThat("Problem while waiting for the cursor to blink",
                    latch.await(10, TimeUnit.SECONDS), equalTo(true));
        } catch (Exception e) {
            fail("Problem while waiting for the cursor to blink");
        }
    }

        int diff = drawableBounds.left - mEditText.getScrollX() + drawablePadding.left;
        assertTrue(diff >= 0 && diff <= 1);
    private void setEditTextHint(final String hint, final int textDirection, final int selection) {
        setEditTextProperties(null, hint, textDirection, selection);
    }

    private void setEditTextText(final String text, final Integer selection) {
        setEditTextProperties(text, null, null, selection);
    }
}
+30 −0
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package android.widget;

import static android.support.test.espresso.action.ViewActions.longClick;
import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
import static android.widget.espresso.DragHandleUtils.onHandleView;
import static android.widget.espresso.FloatingToolbarEspressoUtils.onFloatingToolBarItem;
import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
@@ -49,6 +51,7 @@ import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.Selection;
import android.text.Spannable;
import android.text.InputType;
import android.view.KeyEvent;

import static org.hamcrest.Matchers.anyOf;
@@ -233,6 +236,33 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV
        assertFloatingToolbarIsNotDisplayed();
    }

    @SmallTest
    public void testToolbarAppearsAfterSelection_withFirstStringLtrAlgorithmAndRtlHint()
            throws Exception {
        // after the hint layout change, the floating toolbar was not visible in the case below
        // this test tests that the floating toolbar is displayed on the screen and is visible to
        // user.
        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
        textView.post(new Runnable() {
            @Override
            public void run() {
                textView.setTextDirection(TextView.TEXT_DIRECTION_FIRST_STRONG_LTR);
                textView.setInputType(InputType.TYPE_CLASS_TEXT);
                textView.setSingleLine(true);
                textView.setHint("الروبوت");
            }
        });
        getInstrumentation().waitForIdleSync();

        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView("test"));
        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1));
        onFloatingToolBarItem(withText(com.android.internal.R.string.cut)).perform(click());
        onView(withId(R.id.textview)).perform(longClick());
        sleepForFloatingToolbarPopup();

        assertFloatingToolbarIsDisplayed();
    }

    @SmallTest
    public void testToolbarAndInsertionHandle() throws Exception {
        final String text = "text";
Loading