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

Commit 59ba2b2f authored by Gilles Debunne's avatar Gilles Debunne Committed by Android (Google) Code Review
Browse files

Merge "Bug 5250788: LatinIME slows down as amount of Text increases"

parents 70fa87b5 287d6c6e
Loading
Loading
Loading
Loading
+0 −100
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 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.text;

import java.text.CharacterIterator;

/** {@hide} */
public class CharSequenceIterator implements CharacterIterator {
    private final CharSequence mValue;

    private final int mLength;
    private int mIndex;

    public CharSequenceIterator(CharSequence value) {
        mValue = value;
        mLength = value.length();
        mIndex = 0;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }

    /** {@inheritDoc} */
    public char current() {
        if (mIndex == mLength) {
            return DONE;
        }
        return mValue.charAt(mIndex);
    }

    /** {@inheritDoc} */
    public int getBeginIndex() {
        return 0;
    }

    /** {@inheritDoc} */
    public int getEndIndex() {
        return mLength;
    }

    /** {@inheritDoc} */
    public int getIndex() {
        return mIndex;
    }

    /** {@inheritDoc} */
    public char first() {
        return setIndex(0);
    }

    /** {@inheritDoc} */
    public char last() {
        return setIndex(mLength - 1);
    }

    /** {@inheritDoc} */
    public char next() {
        if (mIndex == mLength) {
            return DONE;
        }
        return setIndex(mIndex + 1);
    }

    /** {@inheritDoc} */
    public char previous() {
        if (mIndex == 0) {
            return DONE;
        }
        return setIndex(mIndex - 1);
    }

    /** {@inheritDoc} */
    public char setIndex(int index) {
        if ((index < 0) || (index > mLength)) {
            throw new IllegalArgumentException("Valid range is [" + 0 + "..." + mLength + "]");
        }
        mIndex = index;
        return current();
    }
}
+6 −4
Original line number Diff line number Diff line
@@ -35,11 +35,11 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
                (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
    }

    private int getCurrentLineTop(Spannable buffer, Layout layout) {
    private static int getCurrentLineTop(Spannable buffer, Layout layout) {
        return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer)));
    }

    private int getPageHeight(TextView widget) {
    private static int getPageHeight(TextView widget) {
        // This calculation does not take into account the view transformations that
        // may have been applied to the child or its containers.  In case of scaling or
        // rotation, the calculated page height may be incorrect.
@@ -196,14 +196,16 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
    /** {@hide} */
    @Override
    protected boolean leftWord(TextView widget, Spannable buffer) {
        mWordIterator.setCharSequence(buffer);
        final int selectionEnd = widget.getSelectionEnd();
        mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
        return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer));
    }

    /** {@hide} */
    @Override
    protected boolean rightWord(TextView widget, Spannable buffer) {
        mWordIterator.setCharSequence(buffer);
        final int selectionEnd = widget.getSelectionEnd();
        mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
        return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer));
    }

+53 −98
Original line number Diff line number Diff line
@@ -17,14 +17,9 @@

package android.text.method;

import android.text.CharSequenceIterator;
import android.text.Editable;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextWatcher;

import java.text.BreakIterator;
import java.text.CharacterIterator;
import java.util.Locale;

/**
@@ -36,8 +31,11 @@ import java.util.Locale;
 * {@hide}
 */
public class WordIterator implements Selection.PositionIterator {
    private CharSequence mCurrent;
    private boolean mCurrentDirty = false;
    // Size of the window for the word iterator, should be greater than the longest word's length
    private static final int WINDOW_WIDTH = 50;

    private String mString;
    private int mOffsetShift;

    private BreakIterator mIterator;

@@ -56,70 +54,40 @@ public class WordIterator implements Selection.PositionIterator {
        mIterator = BreakIterator.getWordInstance(locale);
    }

    private final TextWatcher mWatcher = new TextWatcher() {
        /** {@inheritDoc} */
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // ignored
        }
    public void setCharSequence(CharSequence charSequence, int start, int end) {
        mOffsetShift = Math.max(0, start - WINDOW_WIDTH);
        final int windowEnd = Math.min(charSequence.length(), end + WINDOW_WIDTH);

        /** {@inheritDoc} */
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            mCurrentDirty = true;
        }

        /** {@inheritDoc} */
        public void afterTextChanged(Editable s) {
            // ignored
        }
    };

    public void setCharSequence(CharSequence incoming) {
        // When incoming is different object, move listeners to new sequence
        // and mark as dirty so we reload contents.
        if (mCurrent != incoming) {
            if (mCurrent instanceof Editable) {
                ((Editable) mCurrent).removeSpan(mWatcher);
            }

            if (incoming instanceof Editable) {
                ((Editable) incoming).setSpan(
                        mWatcher, 0, incoming.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
            }

            mCurrent = incoming;
            mCurrentDirty = true;
        }

        if (mCurrentDirty) {
            final CharacterIterator charIterator = new CharSequenceIterator(mCurrent);
            mIterator.setText(charIterator);

            mCurrentDirty = false;
        }
        mString = charSequence.toString().substring(mOffsetShift, windowEnd);
        mIterator.setText(mString);
    }

    /** {@inheritDoc} */
    public int preceding(int offset) {
        int shiftedOffset = offset - mOffsetShift;
        do {
            offset = mIterator.preceding(offset);
            if (offset == BreakIterator.DONE || isOnLetterOrDigit(offset)) {
                break;
            shiftedOffset = mIterator.preceding(shiftedOffset);
            if (shiftedOffset == BreakIterator.DONE) {
                return BreakIterator.DONE;
            }
            if (isOnLetterOrDigit(shiftedOffset)) {
                return shiftedOffset + mOffsetShift;
            }
        } while (true);

        return offset;
    }

    /** {@inheritDoc} */
    public int following(int offset) {
        int shiftedOffset = offset - mOffsetShift;
        do {
            offset = mIterator.following(offset);
            if (offset == BreakIterator.DONE || isAfterLetterOrDigit(offset)) {
                break;
            shiftedOffset = mIterator.following(shiftedOffset);
            if (shiftedOffset == BreakIterator.DONE) {
                return BreakIterator.DONE;
            }
            if (isAfterLetterOrDigit(shiftedOffset)) {
                return shiftedOffset + mOffsetShift;
            }
        } while (true);

        return offset;
    }

    /** If <code>offset</code> is within a word, returns the index of the first character of that
@@ -135,17 +103,18 @@ public class WordIterator implements Selection.PositionIterator {
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getBeginning(int offset) {
        checkOffsetIsValid(offset);
        final int shiftedOffset = offset - mOffsetShift;
        checkOffsetIsValid(shiftedOffset);

        if (isOnLetterOrDigit(offset)) {
            if (mIterator.isBoundary(offset)) {
                return offset;
        if (isOnLetterOrDigit(shiftedOffset)) {
            if (mIterator.isBoundary(shiftedOffset)) {
                return shiftedOffset + mOffsetShift;
            } else {
                return mIterator.preceding(offset);
                return mIterator.preceding(shiftedOffset) + mOffsetShift;
            }
        } else {
            if (isAfterLetterOrDigit(offset)) {
                return mIterator.preceding(offset);
            if (isAfterLetterOrDigit(shiftedOffset)) {
                return mIterator.preceding(shiftedOffset) + mOffsetShift;
            }
        }
        return BreakIterator.DONE;
@@ -164,58 +133,44 @@ public class WordIterator implements Selection.PositionIterator {
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getEnd(int offset) {
        checkOffsetIsValid(offset);
        final int shiftedOffset = offset - mOffsetShift;
        checkOffsetIsValid(shiftedOffset);

        if (isAfterLetterOrDigit(offset)) {
            if (mIterator.isBoundary(offset)) {
                return offset;
        if (isAfterLetterOrDigit(shiftedOffset)) {
            if (mIterator.isBoundary(shiftedOffset)) {
                return shiftedOffset + mOffsetShift;
            } else {
                return mIterator.following(offset);
                return mIterator.following(shiftedOffset) + mOffsetShift;
            }
        } else {
            if (isOnLetterOrDigit(offset)) {
                return mIterator.following(offset);
            if (isOnLetterOrDigit(shiftedOffset)) {
                return mIterator.following(shiftedOffset) + mOffsetShift;
            }
        }
        return BreakIterator.DONE;
    }

    private boolean isAfterLetterOrDigit(int offset) {
        if (offset - 1 >= 0) {
            final char previousChar = mCurrent.charAt(offset - 1);
            if (Character.isLetterOrDigit(previousChar)) return true;
            if (offset - 2 >= 0) {
                final char previousPreviousChar = mCurrent.charAt(offset - 2);
                if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
                    final int codePoint = Character.toCodePoint(previousPreviousChar, previousChar);
                    return Character.isLetterOrDigit(codePoint);
                }
            }
    private boolean isAfterLetterOrDigit(int shiftedOffset) {
        if (shiftedOffset >= 1 && shiftedOffset <= mString.length()) {
            final int codePoint = mString.codePointBefore(shiftedOffset);
            if (Character.isLetterOrDigit(codePoint)) return true;
        }
        return false;
    }

    private boolean isOnLetterOrDigit(int offset) {
        final int length = mCurrent.length();
        if (offset < length) {
            final char currentChar = mCurrent.charAt(offset);
            if (Character.isLetterOrDigit(currentChar)) return true;
            if (offset + 1 < length) {
                final char nextChar = mCurrent.charAt(offset + 1);
                if (Character.isSurrogatePair(currentChar, nextChar)) {
                    final int codePoint = Character.toCodePoint(currentChar, nextChar);
                    return Character.isLetterOrDigit(codePoint);
                }
            }
    private boolean isOnLetterOrDigit(int shiftedOffset) {
        if (shiftedOffset >= 0 && shiftedOffset < mString.length()) {
            final int codePoint = mString.codePointAt(shiftedOffset);
            if (Character.isLetterOrDigit(codePoint)) return true;
        }
        return false;
    }

    private void checkOffsetIsValid(int offset) {
        if (offset < 0 || offset > mCurrent.length()) {
            final String message = "Invalid offset: " + offset +
                    ". Valid range is [0, " + mCurrent.length() + "]";
            throw new IllegalArgumentException(message);
    private void checkOffsetIsValid(int shiftedOffset) {
        if (shiftedOffset < 0 || shiftedOffset > mString.length()) {
            throw new IllegalArgumentException("Invalid offset: " + (shiftedOffset + mOffsetShift) +
                    ". Valid range is [" + mOffsetShift + ", " + (mString.length() + mOffsetShift) +
                    "]");
        }
    }
}
+191 −24

File changed.

Preview size limit exceeded, changes collapsed.

+26 −129
Original line number Diff line number Diff line
@@ -353,8 +353,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    // Set when this TextView gained focus with some text selected. Will start selection mode.
    private boolean mCreatedWithASelection = false;

    // Size of the window for the word iterator, should be greater than the longest word's length
    private static final int WORD_ITERATOR_WINDOW_WIDTH = 50;
    private WordIterator mWordIterator;

    private SpellChecker mSpellChecker;
@@ -6124,7 +6122,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     * not the full view width with padding.
     * {@hide}
     */
    protected void makeNewLayout(int w, int hintWidth,
    protected void makeNewLayout(int wantWidth, int hintWidth,
                                 BoringLayout.Metrics boring,
                                 BoringLayout.Metrics hintBoring,
                                 int ellipsisWidth, boolean bringIntoView) {
@@ -6136,8 +6134,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

        mHighlightPathBogus = true;

        if (w < 0) {
            w = 0;
        if (wantWidth < 0) {
            wantWidth = 0;
        }
        if (hintWidth < 0) {
            hintWidth = 0;
@@ -6157,12 +6155,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            resolveTextDirection();
        }

        mLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment, shouldEllipsize,
        mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
                effectiveEllipsize, effectiveEllipsize == mEllipsize);
        if (switchEllipsize) {
            TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
                    TruncateAt.END : TruncateAt.MARQUEE;
            mSavedMarqueeModeLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment,
            mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
                    shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
        }

@@ -6170,7 +6168,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        mHintLayout = null;

        if (mHint != null) {
            if (shouldEllipsize) hintWidth = w;
            if (shouldEllipsize) hintWidth = wantWidth;

            if (hintBoring == UNKNOWN_BORING) {
                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
@@ -6254,12 +6252,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        prepareCursorControllers();
    }

    private Layout makeSingleLayout(int w, BoringLayout.Metrics boring, int ellipsisWidth,
    private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
            Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
            boolean useSaved) {
        Layout result = null;
        if (mText instanceof Spannable) {
            result = new DynamicLayout(mText, mTransformed, mTextPaint, w,
            result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
                    alignment, mTextDir, mSpacingMult,
                    mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null,
                            ellipsisWidth);
@@ -6272,53 +6270,53 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            }

            if (boring != null) {
                if (boring.width <= w &&
                if (boring.width <= wantWidth &&
                        (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
                    if (useSaved && mSavedLayout != null) {
                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
                                w, alignment, mSpacingMult, mSpacingAdd,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad);
                    } else {
                        result = BoringLayout.make(mTransformed, mTextPaint,
                                w, alignment, mSpacingMult, mSpacingAdd,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad);
                    }

                    if (useSaved) {
                        mSavedLayout = (BoringLayout) result;
                    }
                } else if (shouldEllipsize && boring.width <= w) {
                } else if (shouldEllipsize && boring.width <= wantWidth) {
                    if (useSaved && mSavedLayout != null) {
                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
                                w, alignment, mSpacingMult, mSpacingAdd,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad, effectiveEllipsize,
                                ellipsisWidth);
                    } else {
                        result = BoringLayout.make(mTransformed, mTextPaint,
                                w, alignment, mSpacingMult, mSpacingAdd,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad, effectiveEllipsize,
                                ellipsisWidth);
                    }
                } else if (shouldEllipsize) {
                    result = new StaticLayout(mTransformed,
                            0, mTransformed.length(),
                            mTextPaint, w, alignment, mTextDir, mSpacingMult,
                            mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
                            mSpacingAdd, mIncludePad, effectiveEllipsize,
                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
                } else {
                    result = new StaticLayout(mTransformed, mTextPaint,
                            w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
                            wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
                            mIncludePad);
                }
            } else if (shouldEllipsize) {
                result = new StaticLayout(mTransformed,
                        0, mTransformed.length(),
                        mTextPaint, w, alignment, mTextDir, mSpacingMult,
                        mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
                        mSpacingAdd, mIncludePad, effectiveEllipsize,
                        ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
            } else {
                result = new StaticLayout(mTransformed, mTextPaint,
                        w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
                        wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
                        mIncludePad);
            }
        }
@@ -7749,98 +7747,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     * Create new SpellCheckSpans on the modified region.
     */
    private void updateSpellCheckSpans(int start, int end) {
        if (!isTextEditable() || !isSuggestionsEnabled() || !getSpellChecker().isSessionActive())
            return;
        Editable text = (Editable) mText;

        final int shift = prepareWordIterator(start, end);
        final int shiftedStart = start - shift;
        final int shiftedEnd = end - shift;

        // Move back to the beginning of the current word, if any
        int wordStart = mWordIterator.preceding(shiftedStart);
        int wordEnd;
        if (wordStart == BreakIterator.DONE) {
            wordEnd = mWordIterator.following(shiftedStart);
            if (wordEnd != BreakIterator.DONE) {
                wordStart = mWordIterator.getBeginning(wordEnd);
            }
        } else {
            wordEnd = mWordIterator.getEnd(wordStart);
        }
        if (wordEnd == BreakIterator.DONE) {
            return;
        }

        // We need to expand by one character because we want to include the spans that end/start
        // at position start/end respectively.
        SpellCheckSpan[] spellCheckSpans = text.getSpans(start - 1, end + 1, SpellCheckSpan.class);
        SuggestionSpan[] suggestionSpans = text.getSpans(start - 1, end + 1, SuggestionSpan.class);
        final int numberOfSpellCheckSpans = spellCheckSpans.length;

        // Iterate over the newly added text and schedule new SpellCheckSpans
        while (wordStart <= shiftedEnd) {
            if (wordEnd >= shiftedStart && wordEnd > wordStart) {
                // A new word has been created across the interval boundaries. Remove previous spans
                if (wordStart < shiftedStart && wordEnd > shiftedStart) {
                    removeSpansAt(start, spellCheckSpans, text);
                    removeSpansAt(start, suggestionSpans, text);
                }

                if (wordStart < shiftedEnd && wordEnd > shiftedEnd) {
                    removeSpansAt(end, spellCheckSpans, text);
                    removeSpansAt(end, suggestionSpans, text);
                }

                // Do not create new boundary spans if they already exist
                boolean createSpellCheckSpan = true;
                if (wordEnd == shiftedStart) {
                    for (int i = 0; i < numberOfSpellCheckSpans; i++) {
                        final int spanEnd = text.getSpanEnd(spellCheckSpans[i]);
                        if (spanEnd == start) {
                            createSpellCheckSpan = false;
                            break;
                        }
                    }
                }

                if (wordStart == shiftedEnd) {
                    for (int i = 0; i < numberOfSpellCheckSpans; i++) {
                        final int spanStart = text.getSpanStart(spellCheckSpans[i]);
                        if (spanStart == end) {
                            createSpellCheckSpan = false;
                            break;
                        }
                    }
                }

                if (createSpellCheckSpan) {
                    mSpellChecker.addSpellCheckSpan(wordStart + shift, wordEnd + shift);
                }
            }

            // iterate word by word
            wordEnd = mWordIterator.following(wordEnd);
            if (wordEnd == BreakIterator.DONE) break;
            wordStart = mWordIterator.getBeginning(wordEnd);
            if (wordStart == BreakIterator.DONE) {
                Log.e(LOG_TAG, "No word beginning from " + (wordEnd + shift) + "in " + mText);
                break;
            }
        }

        mSpellChecker.spellCheck();
    }

    private static <T> void removeSpansAt(int offset, T[] spans, Editable text) {
        final int length = spans.length;
        for (int i = 0; i < length; i++) {
            final T span = spans[i];
            final int start = text.getSpanStart(span);
            if (start > offset) continue;
            final int end = text.getSpanEnd(span);
            if (end < offset) continue;
            text.removeSpan(span);
        if (isTextEditable() && isSuggestionsEnabled()) {
            getSpellChecker().spellCheck(start, end);
        }
    }

@@ -8930,15 +8838,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            selectionStart = ((Spanned) mText).getSpanStart(urlSpan);
            selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan);
        } else {
            final int shift = prepareWordIterator(minOffset, maxOffset);
            if (mWordIterator == null) {
                mWordIterator = new WordIterator();
            }
            mWordIterator.setCharSequence(mText, minOffset, maxOffset);

            selectionStart = mWordIterator.getBeginning(minOffset - shift);
            selectionStart = mWordIterator.getBeginning(minOffset);
            if (selectionStart == BreakIterator.DONE) return false;
            selectionStart += shift;

            selectionEnd = mWordIterator.getEnd(maxOffset - shift);
            selectionEnd = mWordIterator.getEnd(maxOffset);
            if (selectionEnd == BreakIterator.DONE) return false;
            selectionEnd += shift;

            if (selectionStart == selectionEnd) {
                // Possible when the word iterator does not properly handle the text's language
@@ -8977,18 +8886,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return packRangeInLong(offset,  offset);
    }

    int prepareWordIterator(int start, int end) {
        if (mWordIterator == null) {
            mWordIterator = new WordIterator();
        }

        final int windowStart = Math.max(0, start - WORD_ITERATOR_WINDOW_WIDTH);
        final int windowEnd = Math.min(mText.length(), end + WORD_ITERATOR_WINDOW_WIDTH);
        mWordIterator.setCharSequence(mText.subSequence(windowStart, windowEnd));

        return windowStart;
    }

    private SpellChecker getSpellChecker() {
        if (mSpellChecker == null) {
            mSpellChecker = new SpellChecker(this);