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

Commit 7e821f05 authored by Qi Wang's avatar Qi Wang
Browse files

Fix the SuggestionSpan overwrite logic.

Previously, the SpellChecker use the start and end positions as the key
to maintain the SuggestionSpan cache.

However, the actual start/end position of the SuggestionSpan may changes
when the text is changed. Hence we may not be able to find the
corresponding SuggestonSpan for a new suggestion with the changed
start/end positions.

In this fix, we deprecated the cache and use Spanned#getSpans to find
overlapped spans instead.

Change-Id: I1e732f335ad745d85a7620e3ced6858551f18232

Fix: 182982338

Test: atest CtsInputMethodTestCases:SpellCheckerTest
Change-Id: I327e5163499cfc978ff8480664a7e728b6ea7249
parent 346aaf46
Loading
Loading
Loading
Loading
+31 −27
Original line number Diff line number Diff line
@@ -20,12 +20,10 @@ import android.annotation.Nullable;
import android.text.Editable;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.WordIterator;
import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.LruCache;
import android.util.Range;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SpellCheckerSession;
@@ -98,10 +96,6 @@ public class SpellChecker implements SpellCheckerSessionListener {

    private Runnable mSpellRunnable;

    private static final int SUGGESTION_SPAN_CACHE_SIZE = 10;
    private final LruCache<Long, SuggestionSpan> mSuggestionSpanCache =
            new LruCache<Long, SuggestionSpan>(SUGGESTION_SPAN_CACHE_SIZE);

    public SpellChecker(TextView textView) {
        mTextView = textView;

@@ -144,7 +138,6 @@ public class SpellChecker implements SpellCheckerSessionListener {

        // Remove existing misspelled SuggestionSpans
        mTextView.removeMisspelledSpans((Editable) mTextView.getText());
        mSuggestionSpanCache.evictAll();
    }

    private void setLocale(Locale locale) {
@@ -410,16 +403,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
                    }
                    if (spellCheckSpanStart >= 0 && spellCheckSpanEnd > spellCheckSpanStart
                            && end > start) {
                        final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
                        final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
                        if (tempSuggestionSpan != null) {
                            if (DBG) {
                                Log.i(TAG, "Remove existing misspelled span. "
                                        + editable.subSequence(start, end));
                            }
                            editable.removeSpan(tempSuggestionSpan);
                            mSuggestionSpanCache.remove(key);
                        }
                        removeErrorSuggestionSpan(editable, start, end, RemoveReason.OBSOLETE);
                    }
                }
                return spellCheckSpan;
@@ -428,6 +412,35 @@ public class SpellChecker implements SpellCheckerSessionListener {
        return null;
    }

    private enum RemoveReason {
        /**
         * Indicates the previous SuggestionSpan is replaced by a new SuggestionSpan.
         */
        REPLACE,
        /**
         * Indicates the previous SuggestionSpan is removed because corresponding text is
         * considered as valid words now.
         */
        OBSOLETE,
    }

    private static void removeErrorSuggestionSpan(
            Editable editable, int start, int end, RemoveReason reason) {
        SuggestionSpan[] spans = editable.getSpans(start, end, SuggestionSpan.class);
        for (SuggestionSpan span : spans) {
            if (editable.getSpanStart(span) == start
                    && editable.getSpanEnd(span) == end
                    && (span.getFlags() & (SuggestionSpan.FLAG_MISSPELLED
                    | SuggestionSpan.FLAG_GRAMMAR_ERROR)) != 0) {
                if (DBG) {
                    Log.i(TAG, "Remove existing misspelled/grammar error span on "
                            + editable.subSequence(start, end) + ", reason: " + reason);
                }
                editable.removeSpan(span);
            }
        }
    }

    @Override
    public void onGetSuggestions(SuggestionsInfo[] results) {
        final Editable editable = (Editable) mTextView.getText();
@@ -543,16 +556,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
        }
        SuggestionSpan suggestionSpan =
                new SuggestionSpan(mTextView.getContext(), suggestions, flags);
        final Long key = Long.valueOf(TextUtils.packRangeInLong(start, end));
        final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
        if (tempSuggestionSpan != null) {
            if (DBG) {
                Log.i(TAG, "Cached span on the same position is cleard. "
                        + editable.subSequence(start, end));
            }
            editable.removeSpan(tempSuggestionSpan);
        }
        mSuggestionSpanCache.put(key, suggestionSpan);
        removeErrorSuggestionSpan(editable, start, end, RemoveReason.REPLACE);
        editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        mTextView.invalidateRegion(start, end, false /* No cursor involved */);