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

Commit 846946ec authored by Keisuke Kuroyanagi's avatar Keisuke Kuroyanagi Committed by Android (Google) Code Review
Browse files

Merge "Make selection end handle stick to selection at line end."

parents f67d4498 7c263ddd
Loading
Loading
Loading
Loading
+61 −28
Original line number Original line Diff line number Diff line
@@ -1015,17 +1015,24 @@ public abstract class Layout {
     * the paragraph's primary direction.
     * the paragraph's primary direction.
     */
     */
    public float getPrimaryHorizontal(int offset) {
    public float getPrimaryHorizontal(int offset) {
        return getPrimaryHorizontal(offset, false /* not clamped */);
        return getPrimaryHorizontal(offset, false /* not clamped */,
                true /* getNewLineStartPosOnLineBreak */);
    }
    }


    /**
    /**
     * Get the primary horizontal position for the specified text offset, but
     * Get the primary horizontal position for the specified text offset, but
     * optionally clamp it so that it doesn't exceed the width of the layout.
     * optionally clamp it so that it doesn't exceed the width of the layout.
     *
     * @param offset the offset to get horizontal position
     * @param clamped whether to clamp the position by using the width of this layout.
     * @param getNewLineStartPosOnLineBreak whether to get the start position of new line when the
     * offset is at automatic line break.
     * @hide
     * @hide
     */
     */
    public float getPrimaryHorizontal(int offset, boolean clamped) {
    public float getPrimaryHorizontal(int offset, boolean clamped,
            boolean getNewLineStartPosOnLineBreak) {
        boolean trailing = primaryIsTrailingPrevious(offset);
        boolean trailing = primaryIsTrailingPrevious(offset);
        return getHorizontal(offset, trailing, clamped);
        return getHorizontal(offset, trailing, clamped, getNewLineStartPosOnLineBreak);
    }
    }


    /**
    /**
@@ -1034,26 +1041,37 @@ public abstract class Layout {
     * the direction other than the paragraph's primary direction.
     * the direction other than the paragraph's primary direction.
     */
     */
    public float getSecondaryHorizontal(int offset) {
    public float getSecondaryHorizontal(int offset) {
        return getSecondaryHorizontal(offset, false /* not clamped */);
        return getSecondaryHorizontal(offset, false /* not clamped */,
                true /* getNewLineStartPosOnLineBreak */);
    }
    }


    /**
    /**
     * Get the secondary horizontal position for the specified text offset, but
     * Get the secondary horizontal position for the specified text offset, but
     * optionally clamp it so that it doesn't exceed the width of the layout.
     * optionally clamp it so that it doesn't exceed the width of the layout.
     *
     * @param offset the offset to get horizontal position
     * @param clamped whether to clamp the position by using the width of this layout.
     * @param getNewLineStartPosOnLineBreak whether to get the start position of new line when the
     * offset is at automatic line break.
     * @hide
     * @hide
     */
     */
    public float getSecondaryHorizontal(int offset, boolean clamped) {
    public float getSecondaryHorizontal(int offset, boolean clamped,
            boolean getNewLineStartPosOnLineBreak) {
        boolean trailing = primaryIsTrailingPrevious(offset);
        boolean trailing = primaryIsTrailingPrevious(offset);
        return getHorizontal(offset, !trailing, clamped);
        return getHorizontal(offset, !trailing, clamped, getNewLineStartPosOnLineBreak);
    }
    }


    private float getHorizontal(int offset, boolean primary) {
    private float getHorizontal(int offset, boolean primary,
        return primary ? getPrimaryHorizontal(offset) : getSecondaryHorizontal(offset);
            boolean getNewLineStartPosOnLineBreak) {
        return primary ? getPrimaryHorizontal(offset, false /* not clamped */,
                getNewLineStartPosOnLineBreak)
                : getSecondaryHorizontal(offset, false /* not clamped */,
                        getNewLineStartPosOnLineBreak);
    }
    }


    private float getHorizontal(int offset, boolean trailing, boolean clamped) {
    private float getHorizontal(int offset, boolean trailing, boolean clamped,
        int line = getLineForOffset(offset);
            boolean getNewLineStartPosOnLineBreak) {

        final int line = getLineForOffset(offset, getNewLineStartPosOnLineBreak);
        return getHorizontal(offset, trailing, line, clamped);
        return getHorizontal(offset, trailing, line, clamped);
    }
    }


@@ -1267,6 +1285,10 @@ public abstract class Layout {
     * beyond the end of the text, you get the last line.
     * beyond the end of the text, you get the last line.
     */
     */
    public int getLineForOffset(int offset) {
    public int getLineForOffset(int offset) {
        return getLineForOffset(offset, true);
    }

    private int getLineForOffset(int offset, boolean getNewLineOnLineBreak) {
        int high = getLineCount(), low = -1, guess;
        int high = getLineCount(), low = -1, guess;


        while (high - low > 1) {
        while (high - low > 1) {
@@ -1278,11 +1300,16 @@ public abstract class Layout {
                low = guess;
                low = guess;
        }
        }


        if (low < 0)
        if (low < 0) {
            return 0;
            return 0;
        else
        } else {
            if (!getNewLineOnLineBreak && low > 0 && getLineStart(low) == offset
                    && mText.charAt(offset - 1) != '\n') {
                return low - 1;
            }
            return low;
            return low;
        }
        }
    }


    /**
    /**
     * Get the character offset on the specified line whose position is
     * Get the character offset on the specified line whose position is
@@ -1315,14 +1342,14 @@ public abstract class Layout {
                false, null);
                false, null);


        final int max;
        final int max;
        if (line == getLineCount() - 1) {
        if (line != getLineCount() - 1 && mText.charAt(lineEndOffset - 1) == '\n') {
            max = lineEndOffset;
        } else {
            max = tl.getOffsetToLeftRightOf(lineEndOffset - lineStartOffset,
            max = tl.getOffsetToLeftRightOf(lineEndOffset - lineStartOffset,
                    !isRtlCharAt(lineEndOffset - 1)) + lineStartOffset;
                    !isRtlCharAt(lineEndOffset - 1)) + lineStartOffset;
        } else {
            max = lineEndOffset;
        }
        }
        int best = lineStartOffset;
        int best = lineStartOffset;
        float bestdist = Math.abs(getHorizontal(best, primary) - horiz);
        float bestdist = Math.abs(getHorizontal(best, primary, true) - horiz);


        for (int i = 0; i < dirs.mDirections.length; i += 2) {
        for (int i = 0; i < dirs.mDirections.length; i += 2) {
            int here = lineStartOffset + dirs.mDirections[i];
            int here = lineStartOffset + dirs.mDirections[i];
@@ -1338,11 +1365,14 @@ public abstract class Layout {
                guess = (high + low) / 2;
                guess = (high + low) / 2;
                int adguess = getOffsetAtStartOf(guess);
                int adguess = getOffsetAtStartOf(guess);


                if (getHorizontal(adguess, primary) * swap >= horiz * swap)
                if (getHorizontal(adguess, primary,
                        adguess == lineStartOffset || adguess != lineEndOffset) * swap
                                >= horiz * swap) {
                    high = guess;
                    high = guess;
                else
                } else {
                    low = guess;
                    low = guess;
                }
                }
            }


            if (low < here + 1)
            if (low < here + 1)
                low = here + 1;
                low = here + 1;
@@ -1351,9 +1381,11 @@ public abstract class Layout {
                int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset;
                int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset;
                low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset;
                low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset;
                if (low >= here && low < there) {
                if (low >= here && low < there) {
                    float dist = Math.abs(getHorizontal(low, primary) - horiz);
                    float dist = Math.abs(getHorizontal(low, primary,
                            low == lineStartOffset || low != lineEndOffset) - horiz);
                    if (aft < there) {
                    if (aft < there) {
                        float other = Math.abs(getHorizontal(aft, primary) - horiz);
                        float other = Math.abs(getHorizontal(aft, primary,
                                aft == lineStartOffset || aft != lineEndOffset) - horiz);


                        if (other < dist) {
                        if (other < dist) {
                            dist = other;
                            dist = other;
@@ -1368,7 +1400,8 @@ public abstract class Layout {
                }
                }
            }
            }


            float dist = Math.abs(getHorizontal(here, primary) - horiz);
            float dist = Math.abs(getHorizontal(here, primary,
                    here == lineStartOffset || here != lineEndOffset) - horiz);


            if (dist < bestdist) {
            if (dist < bestdist) {
                bestdist = dist;
                bestdist = dist;
@@ -1376,10 +1409,10 @@ public abstract class Layout {
            }
            }
        }
        }


        float dist = Math.abs(getHorizontal(max, primary) - horiz);
        float dist = Math.abs(getHorizontal(max, primary,
                max == lineStartOffset || max != lineEndOffset) - horiz);


        if (dist <= bestdist) {
        if (dist <= bestdist) {
            bestdist = dist;
            best = max;
            best = max;
        }
        }


@@ -1573,8 +1606,9 @@ public abstract class Layout {
        int bottom = getLineTop(line+1);
        int bottom = getLineTop(line+1);


        boolean clamped = shouldClampCursor(line);
        boolean clamped = shouldClampCursor(line);
        float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
        float h1 = getPrimaryHorizontal(point, clamped, true) - 0.5f;
        float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point, clamped) - 0.5f : h1;
        float h2 = isLevelBoundary(point)
                ? getSecondaryHorizontal(point, clamped, true) - 0.5f : h1;


        int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
        int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
                   TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
                   TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
@@ -1691,8 +1725,7 @@ public abstract class Layout {
        }
        }


        int startline = getLineForOffset(start);
        int startline = getLineForOffset(start);
        int endline = getLineForOffset(end);
        int endline = getLineForOffset(end, false);

        int top = getLineTop(startline);
        int top = getLineTop(startline);
        int bottom = getLineBottom(endline);
        int bottom = getLineBottom(endline);


+41 −10
Original line number Original line Diff line number Diff line
@@ -1954,10 +1954,11 @@ public class Editor {
        }
        }


        boolean clamped = layout.shouldClampCursor(line);
        boolean clamped = layout.shouldClampCursor(line);
        updateCursorPosition(0, top, middle, layout.getPrimaryHorizontal(offset, clamped));
        updateCursorPosition(0, top, middle, layout.getPrimaryHorizontal(offset, clamped, true));


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


@@ -4380,7 +4381,7 @@ public class Editor {
                    updateSelection(offset);
                    updateSelection(offset);
                    addPositionToTouchUpFilter(offset);
                    addPositionToTouchUpFilter(offset);
                }
                }
                final int line = layout.getLineForOffset(offset);
                final int line = getLineForOffset(layout, offset);
                mPrevLine = line;
                mPrevLine = line;


                mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX
                mPositionX = getCursorHorizontalPosition(layout, offset) - mHotspotX
@@ -4407,6 +4408,15 @@ public class Editor {
            return (int) (getHorizontal(layout, offset) - 0.5f);
            return (int) (getHorizontal(layout, offset) - 0.5f);
        }
        }


        /**
         * @param layout Text layout.
         * @param offset Character offset for the cursor.
         * @return The line the cursor should be at.
         */
        int getLineForOffset(Layout layout, int offset) {
            return layout.getLineForOffset(offset);
        }

        @Override
        @Override
        public void updatePosition(int parentPositionX, int parentPositionY,
        public void updatePosition(int parentPositionX, int parentPositionY,
                boolean parentPositionChanged, boolean parentScrolled) {
                boolean parentPositionChanged, boolean parentScrolled) {
@@ -4835,7 +4845,7 @@ public class Editor {
                    || !isStartHandle() && initialOffset <= anotherHandleOffset) {
                    || !isStartHandle() && initialOffset <= anotherHandleOffset) {
                // Handles have crossed, bound it to the first selected line and
                // Handles have crossed, bound it to the first selected line and
                // adjust by word / char as normal.
                // adjust by word / char as normal.
                currLine = layout.getLineForOffset(anotherHandleOffset);
                currLine = getLineForOffset(layout, anotherHandleOffset, !isStartHandle());
                initialOffset = getOffsetAtCoordinate(layout, currLine, x);
                initialOffset = getOffsetAtCoordinate(layout, currLine, x);
            }
            }


@@ -4907,14 +4917,18 @@ public class Editor {
            if (isExpanding) {
            if (isExpanding) {
                // User is increasing the selection.
                // User is increasing the selection.
                int wordBoundary = isStartHandle() ? wordStart : wordEnd;
                int wordBoundary = isStartHandle() ? wordStart : wordEnd;
                final boolean snapToWord = (!mInWord
                final boolean atLineBoundary = layout.getLineStart(currLine) == offset
                        || layout.getLineEnd(currLine) == offset;
                final boolean atWordBoundary = getWordIteratorWithText().isBoundary(offset);
                final boolean snapToWord = !(atLineBoundary && atWordBoundary)
                        && (!mInWord
                                || (isStartHandle() ? currLine < mPrevLine : currLine > mPrevLine))
                                || (isStartHandle() ? currLine < mPrevLine : currLine > mPrevLine))
                                        && atRtl == isAtRtlRun(layout, wordBoundary);
                                        && atRtl == isAtRtlRun(layout, wordBoundary);
                if (snapToWord) {
                if (snapToWord) {
                    // Sometimes words can be broken across lines (Chinese, hyphenation).
                    // Sometimes words can be broken across lines (Chinese, hyphenation).
                    // We still snap to the word boundary but we only use the letters on the
                    // We still snap to the word boundary but we only use the letters on the
                    // current line to determine if the user is far enough into the word to snap.
                    // current line to determine if the user is far enough into the word to snap.
                    if (layout.getLineForOffset(wordBoundary) != currLine) {
                    if (getLineForOffset(layout, wordBoundary) != currLine) {
                        wordBoundary = isStartHandle()
                        wordBoundary = isStartHandle()
                                ? layout.getLineStart(currLine) : layout.getLineEnd(currLine);
                                ? layout.getLineStart(currLine) : layout.getLineEnd(currLine);
                    }
                    }
@@ -5062,12 +5076,29 @@ public class Editor {
        }
        }


        private float getHorizontal(@NonNull Layout layout, int offset, boolean startHandle) {
        private float getHorizontal(@NonNull Layout layout, int offset, boolean startHandle) {
            final int line = layout.getLineForOffset(offset);
            final int line = getLineForOffset(layout, offset);
            final int offsetToCheck = startHandle ? offset : Math.max(offset - 1, 0);
            final int offsetToCheck = startHandle ? offset : Math.max(offset - 1, 0);
            final boolean isRtlChar = layout.isRtlCharAt(offsetToCheck);
            final boolean isRtlChar = layout.isRtlCharAt(offsetToCheck);
            final boolean isRtlParagraph = layout.getParagraphDirection(line) == -1;
            final boolean isRtlParagraph = layout.getParagraphDirection(line) == -1;
            return (isRtlChar == isRtlParagraph)
            return (isRtlChar == isRtlParagraph)
                    ? layout.getPrimaryHorizontal(offset) : layout.getSecondaryHorizontal(offset);
                    ? layout.getPrimaryHorizontal(offset, false, startHandle)
                            : layout.getSecondaryHorizontal(offset, false, startHandle);
        }

        @Override
        public int getLineForOffset(@NonNull Layout layout, int offset) {
            return getLineForOffset(layout, offset, isStartHandle());
        }

        private int getLineForOffset(@NonNull Layout layout, int offset, boolean startHandle) {
            final int line = layout.getLineForOffset(offset);
            if (!startHandle && line > 0 && layout.getLineStart(line) == offset
                    && mTextView.getText().charAt(offset - 1) != '\n') {
                // If end handle is at a line break in a paragraph, the handle should be at the
                // previous line.
                return line - 1;
            }
            return line;
        }
        }


        @Override
        @Override
+1 −1
Original line number Original line Diff line number Diff line
@@ -7972,7 +7972,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        // right where it is most likely to be annoying.
        // right where it is most likely to be annoying.
        final boolean clamped = grav > 0;
        final boolean clamped = grav > 0;
        // FIXME: Is it okay to truncate this, or should we round?
        // FIXME: Is it okay to truncate this, or should we round?
        final int x = (int) layout.getPrimaryHorizontal(offset, clamped);
        final int x = (int) layout.getPrimaryHorizontal(offset, clamped, true);
        final int top = layout.getLineTop(line);
        final int top = layout.getLineTop(line);
        final int bottom = layout.getLineTop(line + 1);
        final int bottom = layout.getLineTop(line + 1);


+23 −0
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import static android.widget.espresso.TextViewActions.dragHandle;
import static android.widget.espresso.TextViewActions.Handle;
import static android.widget.espresso.TextViewActions.Handle;
import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
import static android.widget.espresso.TextViewAssertions.handleIsOnLine;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
import static android.widget.espresso.TextViewAssertions.hasSelection;
import static android.widget.espresso.TextViewAssertions.hasSelection;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
@@ -464,6 +465,28 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV
        onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
        onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
    }
    }


    public void testSelectionHandles_multiLine_japanese() throws Exception {
        onView(withId(R.id.textview)).perform(click());
        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
        final StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 100; ++i) {
            builder.append("\u3042\u3044\u3046\u3048\u304A");
            onView(withId(R.id.textview)).perform(replaceText(builder.toString()));
            final int lineEnd = textView.getLayout().getLineEnd(0);
            if (lineEnd < builder.length()) {
                break;
            }
        }

        onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(3));

        final int lineEnd = textView.getLayout().getLineEnd(0);
        onHandleView(com.android.internal.R.id.selection_end_handle)
                .perform(dragHandle(textView, Handle.SELECTION_END, lineEnd, true, false));
        onHandleView(com.android.internal.R.id.selection_end_handle)
                .check(handleIsOnLine(textView, 0));
    }

    public void testSelectionHandles_multiLine_rtl() throws Exception {
    public void testSelectionHandles_multiLine_rtl() throws Exception {
        // Arabic text.
        // Arabic text.
        final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
        final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
+64 −26
Original line number Original line Diff line number Diff line
@@ -331,15 +331,37 @@ public final class TextViewActions {
     */
     */
    public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
    public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
            boolean primary) {
            boolean primary) {
        return dragHandle(textView, handleType, endIndex, primary, true);
    }

    /**
     * Returns an action that tap then drags on the handle from the current position to endIndex on
     * the TextView.<br>
     * <br>
     * View constraints:
     * <ul>
     * <li>must be a TextView's drag-handle displayed on screen
     * <ul>
     *
     * @param textView TextView the handle is on
     * @param handleType Type of the handle
     * @param endIndex The index of the TextView's text to end the drag at
     * @param primary whether to use primary direction to get coordinate form index when endIndex is
     * at a direction boundary.
     * @param getNewLineStartPosOnLineBreak whether to use new line start coordinate on a line break
     * within a paragraph.
     */
    public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex,
            boolean primary, boolean getNewLineStartPosOnLineBreak) {
        return actionWithAssertions(
        return actionWithAssertions(
                new DragAction(
                new DragAction(
                        DragAction.Drag.TAP,
                        DragAction.Drag.TAP,
                        new CurrentHandleCoordinates(textView),
                        new CurrentHandleCoordinates(textView),
                        new HandleCoordinates(textView, handleType, endIndex, primary),
                        new HandleCoordinates(textView, handleType, endIndex, primary,
                                getNewLineStartPosOnLineBreak),
                        Press.FINGER,
                        Press.FINGER,
                        Editor.HandleView.class));
                        Editor.HandleView.class));
    }
    }

    /**
    /**
     * A provider of the x, y coordinates of the handle dragging point.
     * A provider of the x, y coordinates of the handle dragging point.
     */
     */
@@ -402,13 +424,16 @@ public final class TextViewActions {
        private final Handle mHandleType;
        private final Handle mHandleType;
        private final int mIndex;
        private final int mIndex;
        private final boolean mPrimary;
        private final boolean mPrimary;
        private final boolean mGetNewLineStartPosOnLineBreak;
        private final String mActionDescription;
        private final String mActionDescription;


        public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary) {
        public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary,
                boolean getNewLineStartPosOnLineBreak) {
            mTextView = textView;
            mTextView = textView;
            mHandleType = handleType;
            mHandleType = handleType;
            mIndex = index;
            mIndex = index;
            mPrimary = primary;
            mPrimary = primary;
            mGetNewLineStartPosOnLineBreak = getNewLineStartPosOnLineBreak;
            mActionDescription = "Could not locate " + handleType.toString()
            mActionDescription = "Could not locate " + handleType.toString()
                    + " handle that points text index: " + index
                    + " handle that points text index: " + index
                    + " (" + (primary ? "primary" : "secondary" ) + ")";
                    + " (" + (primary ? "primary" : "secondary" ) + ")";
@@ -445,9 +470,10 @@ public final class TextViewActions {
            final float currentX = handleView.getHorizontal(layout, currentOffset);
            final float currentX = handleView.getHorizontal(layout, currentOffset);
            final float currentY = layout.getLineTop(currentLine);
            final float currentY = layout.getLineTop(currentLine);
            final float[] currentCoordinates =
            final float[] currentCoordinates =
                    TextCoordinates.convertToScreenCoordinates(mTextView, currentX, currentY);
                    convertToScreenCoordinates(mTextView, currentX, currentY);
            final float[] targetCoordinates =
            final float[] targetCoordinates =
                    (new TextCoordinates(mIndex, mPrimary)).calculateCoordinates(mTextView);
                    (new TextCoordinates(mIndex, mPrimary, mGetNewLineStartPosOnLineBreak))
                            .calculateCoordinates(mTextView);
            final Rect bounds = new Rect();
            final Rect bounds = new Rect();
            view.getBoundsOnScreen(bounds);
            view.getBoundsOnScreen(bounds);
            final Rect visibleDisplayBounds = new Rect();
            final Rect visibleDisplayBounds = new Rect();
@@ -485,23 +511,27 @@ public final class TextViewActions {


        private final int mIndex;
        private final int mIndex;
        private final boolean mPrimary;
        private final boolean mPrimary;
        private final boolean mGetNewLineStartPosOnLineBreak;
        private final String mActionDescription;
        private final String mActionDescription;


        public TextCoordinates(int index) {
        public TextCoordinates(int index) {
            this(index, true);
            this(index, true, true);
        }
        }


        public TextCoordinates(int index, boolean primary) {
        public TextCoordinates(int index, boolean primary, boolean getNewLineStartPosOnLineBreak) {
            mIndex = index;
            mIndex = index;
            mPrimary = primary;
            mPrimary = primary;
            mGetNewLineStartPosOnLineBreak = getNewLineStartPosOnLineBreak;
            mActionDescription = "Could not locate text at index: " + mIndex
            mActionDescription = "Could not locate text at index: " + mIndex
                    + " (" + (primary ? "primary" : "secondary" ) + ")";
                    + " (" + (primary ? "primary" : "secondary" )
                    + ", mGetNewLineStartPosOnLineBreak: " + mGetNewLineStartPosOnLineBreak + ")";
        }
        }


        @Override
        @Override
        public float[] calculateCoordinates(View view) {
        public float[] calculateCoordinates(View view) {
            try {
            try {
                return locateTextAtIndex((TextView) view, mIndex, mPrimary);
                return locateTextAtIndex((TextView) view, mIndex, mPrimary,
                        mGetNewLineStartPosOnLineBreak);
            } catch (ClassCastException e) {
            } catch (ClassCastException e) {
                throw new PerformException.Builder()
                throw new PerformException.Builder()
                        .withActionDescription(mActionDescription)
                        .withActionDescription(mActionDescription)
@@ -520,17 +550,26 @@ public final class TextViewActions {
        /**
        /**
         * @throws StringIndexOutOfBoundsException
         * @throws StringIndexOutOfBoundsException
         */
         */
        private float[] locateTextAtIndex(TextView textView, int index, boolean primary) {
        private float[] locateTextAtIndex(TextView textView, int index, boolean primary,
                boolean getNewLineStartPosOnLineBreak) {
            if (index < 0 || index > textView.getText().length()) {
            if (index < 0 || index > textView.getText().length()) {
                throw new StringIndexOutOfBoundsException(index);
                throw new StringIndexOutOfBoundsException(index);
            }
            }
            final Layout layout = textView.getLayout();
            final Layout layout = textView.getLayout();
            final int line = layout.getLineForOffset(index);

            int line = layout.getLineForOffset(index);
            if (!getNewLineStartPosOnLineBreak && line > 0 && layout.getLineStart(line) == index
                    && textView.getText().charAt(index - 1) != '\n') {
                line = line - 1;
            }
            return convertToScreenCoordinates(textView,
            return convertToScreenCoordinates(textView,
                    (primary ? layout.getPrimaryHorizontal(index)
                    (primary ? layout.getPrimaryHorizontal(index, false,
                            : layout.getSecondaryHorizontal(index)),
                            getNewLineStartPosOnLineBreak)
                            : layout.getSecondaryHorizontal(index, false,
                                    getNewLineStartPosOnLineBreak)),
                    layout.getLineTop(line));
                    layout.getLineTop(line));
        }
        }
    }


    /**
    /**
     * Convert TextView's local coordinates to on screen coordinates.
     * Convert TextView's local coordinates to on screen coordinates.
@@ -546,4 +585,3 @@ public final class TextViewActions {
                y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] };
                y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] };
    }
    }
}
}
}
Loading