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
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index a63305e..287c182 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -20,12 +20,10 @@
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 @@
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 @@
// Remove existing misspelled SuggestionSpans
mTextView.removeMisspelledSpans((Editable) mTextView.getText());
- mSuggestionSpanCache.evictAll();
}
private void setLocale(Locale locale) {
@@ -410,16 +403,7 @@
}
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 @@
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 @@
}
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 */);