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

Commit f656030a authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Bug 5437846: Crash in SpellChecker

The cached mText field from TextView is not constant overtime.
It is especially changed whne the first character of text is added.
Retrieve the actual current value everytime we use it.

Change-Id: Ie297a3292106879621e54a22e5d03444d442ec96
parent 9dcd5afa
Loading
Loading
Loading
Loading
+46 −41
Original line number Diff line number Diff line
@@ -44,7 +44,6 @@ public class SpellChecker implements SpellCheckerSessionListener {
    private final static int MAX_SPELL_BATCH_SIZE = 50;

    private final TextView mTextView;
    private final Editable mText;

    final SpellCheckerSession mSpellCheckerSession;
    final int mCookie;
@@ -64,7 +63,6 @@ public class SpellChecker implements SpellCheckerSessionListener {

    public SpellChecker(TextView textView) {
        mTextView = textView;
        mText = (Editable) textView.getText();

        final TextServicesManager textServicesManager = (TextServicesManager) textView.getContext().
                getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
@@ -121,9 +119,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
        return mLength - 1;
    }

    private void addSpellCheckSpan(int start, int end) {
    private void addSpellCheckSpan(Editable editable, int start, int end) {
        final int index = nextSpellCheckSpanIndex();
        mText.setSpan(mSpellCheckSpans[index], start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        editable.setSpan(mSpellCheckSpans[index], start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        mIds[index] = mSpanSequenceCounter++;
    }

@@ -168,8 +166,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
    private void spellCheck() {
        if (mSpellCheckerSession == null) return;

        final int selectionStart = Selection.getSelectionStart(mText);
        final int selectionEnd = Selection.getSelectionEnd(mText);
        Editable editable = (Editable) mTextView.getText();
        final int selectionStart = Selection.getSelectionStart(editable);
        final int selectionEnd = Selection.getSelectionEnd(editable);

        TextInfo[] textInfos = new TextInfo[mLength];
        int textInfosCount = 0;
@@ -178,12 +177,12 @@ public class SpellChecker implements SpellCheckerSessionListener {
            final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
            if (spellCheckSpan.isSpellCheckInProgress()) continue;

            final int start = mText.getSpanStart(spellCheckSpan);
            final int end = mText.getSpanEnd(spellCheckSpan);
            final int start = editable.getSpanStart(spellCheckSpan);
            final int end = editable.getSpanEnd(spellCheckSpan);

            // Do not check this word if the user is currently editing it
            if (start >= 0 && end > start && (selectionEnd < start || selectionStart > end)) {
                final String word = mText.subSequence(start, end).toString();
                final String word = editable.subSequence(start, end).toString();
                spellCheckSpan.setSpellCheckInProgress(true);
                textInfos[textInfosCount++] = new TextInfo(word, mCookie, mIds[i]);
            }
@@ -202,6 +201,8 @@ public class SpellChecker implements SpellCheckerSessionListener {

    @Override
    public void onGetSuggestions(SuggestionsInfo[] results) {
        Editable editable = (Editable) mTextView.getText();

        for (int i = 0; i < results.length; i++) {
            SuggestionsInfo suggestionsInfo = results[i];
            if (suggestionsInfo.getCookie() != mCookie) continue;
@@ -217,9 +218,9 @@ public class SpellChecker implements SpellCheckerSessionListener {

                    SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
                    if (!isInDictionary && looksLikeTypo) {
                        createMisspelledSuggestionSpan(suggestionsInfo, spellCheckSpan);
                        createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan);
                    }
                    mText.removeSpan(spellCheckSpan);
                    editable.removeSpan(spellCheckSpan);
                    break;
                }
            }
@@ -234,18 +235,18 @@ public class SpellChecker implements SpellCheckerSessionListener {
        }
    }

    private void createMisspelledSuggestionSpan(SuggestionsInfo suggestionsInfo,
            SpellCheckSpan spellCheckSpan) {
        final int start = mText.getSpanStart(spellCheckSpan);
        final int end = mText.getSpanEnd(spellCheckSpan);
    private void createMisspelledSuggestionSpan(Editable editable,
            SuggestionsInfo suggestionsInfo, SpellCheckSpan spellCheckSpan) {
        final int start = editable.getSpanStart(spellCheckSpan);
        final int end = editable.getSpanEnd(spellCheckSpan);

        // Other suggestion spans may exist on that region, with identical suggestions, filter
        // them out to avoid duplicates. First, filter suggestion spans on that exact region.
        SuggestionSpan[] suggestionSpans = mText.getSpans(start, end, SuggestionSpan.class);
        SuggestionSpan[] suggestionSpans = editable.getSpans(start, end, SuggestionSpan.class);
        final int length = suggestionSpans.length;
        for (int i = 0; i < length; i++) {
            final int spanStart = mText.getSpanStart(suggestionSpans[i]);
            final int spanEnd = mText.getSpanEnd(suggestionSpans[i]);
            final int spanStart = editable.getSpanStart(suggestionSpans[i]);
            final int spanEnd = editable.getSpanEnd(suggestionSpans[i]);
            if (spanStart != start || spanEnd != end) {
                suggestionSpans[i] = null;
                break;
@@ -293,7 +294,7 @@ public class SpellChecker implements SpellCheckerSessionListener {

        SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions,
                SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
        mText.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        // TODO limit to the word rectangle region
        mTextView.invalidate();
@@ -304,22 +305,24 @@ public class SpellChecker implements SpellCheckerSessionListener {
        private Object mRange = new Object();

        public void init(int start, int end) {
            mText.setSpan(mRange, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            ((Editable) mTextView.getText()).setSpan(mRange, start, end,
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        public void close() {
            mText.removeSpan(mRange);
            ((Editable) mTextView.getText()).removeSpan(mRange);
        }

        public boolean isDone() {
            return mText.getSpanStart(mRange) < 0;
            return ((Editable) mTextView.getText()).getSpanStart(mRange) < 0;
        }

        public void parse() {
            Editable editable = (Editable) mTextView.getText();
            // Iterate over the newly added text and schedule new SpellCheckSpans
            final int start = mText.getSpanStart(mRange);
            final int end = mText.getSpanEnd(mRange);
            mWordIterator.setCharSequence(mText, start, end);
            final int start = editable.getSpanStart(mRange);
            final int end = editable.getSpanEnd(mRange);
            mWordIterator.setCharSequence(editable, start, end);

            // Move back to the beginning of the current word, if any
            int wordStart = mWordIterator.preceding(start);
@@ -333,14 +336,16 @@ public class SpellChecker implements SpellCheckerSessionListener {
                wordEnd = mWordIterator.getEnd(wordStart);
            }
            if (wordEnd == BreakIterator.DONE) {
                mText.removeSpan(mRange);
                editable.removeSpan(mRange);
                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 = mText.getSpans(start-1, end+1, SpellCheckSpan.class);
            SuggestionSpan[] suggestionSpans = mText.getSpans(start-1, end+1, SuggestionSpan.class);
            SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1,
                    SpellCheckSpan.class);
            SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1,
                    SuggestionSpan.class);

            int nbWordsChecked = 0;
            boolean scheduleOtherSpellCheck = false;
@@ -350,20 +355,20 @@ public class SpellChecker implements SpellCheckerSessionListener {
                    // A new word has been created across the interval boundaries with this edit.
                    // Previous spans (ended on start / started on end) removed, not valid anymore
                    if (wordStart < start && wordEnd > start) {
                        removeSpansAt(start, spellCheckSpans);
                        removeSpansAt(start, suggestionSpans);
                        removeSpansAt(editable, start, spellCheckSpans);
                        removeSpansAt(editable, start, suggestionSpans);
                    }

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

                    // Do not create new boundary spans if they already exist
                    boolean createSpellCheckSpan = true;
                    if (wordEnd == start) {
                        for (int i = 0; i < spellCheckSpans.length; i++) {
                            final int spanEnd = mText.getSpanEnd(spellCheckSpans[i]);
                            final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]);
                            if (spanEnd == start) {
                                createSpellCheckSpan = false;
                                break;
@@ -373,7 +378,7 @@ public class SpellChecker implements SpellCheckerSessionListener {

                    if (wordStart == end) {
                        for (int i = 0; i < spellCheckSpans.length; i++) {
                            final int spanStart = mText.getSpanStart(spellCheckSpans[i]);
                            final int spanStart = editable.getSpanStart(spellCheckSpans[i]);
                            if (spanStart == end) {
                                createSpellCheckSpan = false;
                                break;
@@ -386,7 +391,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
                            scheduleOtherSpellCheck = true;
                            break;
                        }
                        addSpellCheckSpan(wordStart, wordEnd);
                        addSpellCheckSpan(editable, wordStart, wordEnd);
                        nbWordsChecked++;
                    }
                }
@@ -401,23 +406,23 @@ public class SpellChecker implements SpellCheckerSessionListener {
            }

            if (scheduleOtherSpellCheck) {
                mText.setSpan(mRange, wordStart, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                editable.setSpan(mRange, wordStart, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            } else {
                mText.removeSpan(mRange);
                editable.removeSpan(mRange);
            }

            spellCheck();
        }

        private <T> void removeSpansAt(int offset, T[] spans) {
        private <T> void removeSpansAt(Editable editable, int offset, T[] spans) {
            final int length = spans.length;
            for (int i = 0; i < length; i++) {
                final T span = spans[i];
                final int start = mText.getSpanStart(span);
                final int start = editable.getSpanStart(span);
                if (start > offset) continue;
                final int end = mText.getSpanEnd(span);
                final int end = editable.getSpanEnd(span);
                if (end < offset) continue;
                mText.removeSpan(span);
                editable.removeSpan(span);
            }
        }
    }