From 7f2343de99be99f27bf1d38422b199ea1a2666bf Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Tue, 18 May 2021 18:50:34 +0800 Subject: [PATCH] Fix a bug in SpellCheckSpan update logic. The the text is changed, we need to remove all affected SpellCheckSpan. However, the previous check logic can not work correctly, and may leave some out-of-date SpellCheckSpan, which causes problem when the SpellChecker tries to apply suggestions on those spans later. Also fixed the bug that the offset is not calculated correctly when we validate spell checker suggestions. Fix: 188107864 Change-Id: I8dc806d96b6a75255277d6de013305557835cb6d Test: atest CtsInputMethodTestCases:SpellCheckerTest Test: atest android.widget.cts.TextViewTest --- core/java/android/widget/SpellChecker.java | 18 ++++++++++++------ core/java/android/widget/TextView.java | 15 +++++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index 7c04b1cc195b0..2f7b85e2ab9b5 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -375,6 +375,13 @@ public class SpellChecker implements SpellCheckerSessionListener { final int sequenceNumber = suggestionsInfo.getSequence(); for (int k = 0; k < mLength; ++k) { if (sequenceNumber == mIds[k]) { + final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[k]; + final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan); + if (spellCheckSpanStart < 0) { + // Skips the suggestion if the matched span has been removed. + return null; + } + final int attributes = suggestionsInfo.getSuggestionsAttributes(); final boolean isInDictionary = ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0); @@ -383,7 +390,11 @@ public class SpellChecker implements SpellCheckerSessionListener { final boolean looksLikeGrammarError = ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR) > 0); - final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[k]; + // Validates the suggestions range in case the SpellCheckSpan is out-of-date but not + // removed as expected. + if (spellCheckSpanStart + offset + length > editable.length()) { + return spellCheckSpan; + } //TODO: we need to change that rule for results from a sentence-level spell // checker that will probably be in dictionary. if (!isInDictionary && (looksLikeTypo || looksLikeGrammarError)) { @@ -393,7 +404,6 @@ public class SpellChecker implements SpellCheckerSessionListener { // Valid word -- isInDictionary || !looksLikeTypo // Allow the spell checker to remove existing misspelled span by // overwriting the span over the same place - final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan); final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan); final int start; final int end; @@ -461,7 +471,6 @@ public class SpellChecker implements SpellCheckerSessionListener { @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { final Editable editable = (Editable) mTextView.getText(); - final int sentenceLength = editable.length(); for (int i = 0; i < results.length; ++i) { final SentenceSuggestionsInfo ssi = results[i]; if (ssi == null) { @@ -475,9 +484,6 @@ public class SpellChecker implements SpellCheckerSessionListener { } final int offset = ssi.getOffsetAt(j); final int length = ssi.getLengthAt(j); - if (offset < 0 || offset + length > sentenceLength) { - continue; - } final SpellCheckSpan scs = onGetSuggestionsInternal( suggestionsInfo, offset, length); if (spellCheckSpan == null && scs != null) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1a37b595287d1..374dc1cba5dbd 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -10746,12 +10746,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Editable text = (Editable) mText; T[] spans = text.getSpans(start, end, type); - final int length = spans.length; - for (int i = 0; i < length; i++) { - final int spanStart = text.getSpanStart(spans[i]); - final int spanEnd = text.getSpanEnd(spans[i]); - if (spanEnd == start || spanStart == end) break; - text.removeSpan(spans[i]); + ArrayList spansToRemove = new ArrayList<>(); + for (T span : spans) { + final int spanStart = text.getSpanStart(span); + final int spanEnd = text.getSpanEnd(span); + if (spanEnd == start || spanStart == end) continue; + spansToRemove.add(span); + } + for (T span : spansToRemove) { + text.removeSpan(span); } }