Skip to content

Commit 176cd0d

Browse files
author
Gilles Debunne
committed
Bug 5384674: When only one suggestion is returned, it is displayed twice
Two separate issues here: - The results of the spell checker may be identical to the one set by the IME. Since we merge the spans, the entries are duplicated. Filter spell checker results to avoid these duplicates. - When the text is saved on rotation, the spans are saved and restored. Since we start a new spell check when the window is attached, it also doubles the size. Change-Id: I21e1a5ae1b264bc97f44d762e4589bf520c6c19c
1 parent eba3d92 commit 176cd0d

File tree

2 files changed

+80
-48
lines changed

2 files changed

+80
-48
lines changed

core/java/android/widget/SpellChecker.java

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030

3131
import com.android.internal.util.ArrayUtils;
3232

33-
import java.util.Locale;
34-
3533

3634
/**
3735
* Helper class for TextView. Bridge between the TextView and the Dictionnary service.
@@ -167,15 +165,12 @@ public void spellCheck() {
167165

168166
@Override
169167
public void onGetSuggestions(SuggestionsInfo[] results) {
170-
final Editable editable = (Editable) mTextView.getText();
171168
for (int i = 0; i < results.length; i++) {
172169
SuggestionsInfo suggestionsInfo = results[i];
173170
if (suggestionsInfo.getCookie() != mCookie) continue;
174171
final int sequenceNumber = suggestionsInfo.getSequence();
175172

176173
for (int j = 0; j < mLength; j++) {
177-
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
178-
179174
if (sequenceNumber == mIds[j]) {
180175
final int attributes = suggestionsInfo.getSuggestionsAttributes();
181176
boolean isInDictionary =
@@ -184,31 +179,78 @@ public void onGetSuggestions(SuggestionsInfo[] results) {
184179
((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);
185180

186181
if (!isInDictionary && looksLikeTypo) {
187-
String[] suggestions = getSuggestions(suggestionsInfo);
188-
SuggestionSpan suggestionSpan = new SuggestionSpan(
189-
mTextView.getContext(), suggestions,
190-
SuggestionSpan.FLAG_EASY_CORRECT |
191-
SuggestionSpan.FLAG_MISSPELLED);
192-
final int start = editable.getSpanStart(spellCheckSpan);
193-
final int end = editable.getSpanEnd(spellCheckSpan);
194-
editable.setSpan(suggestionSpan, start, end,
195-
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
196-
// TODO limit to the word rectangle region
197-
mTextView.invalidate();
182+
createMisspelledSuggestionSpan(suggestionsInfo, mSpellCheckSpans[j]);
198183
}
199-
editable.removeSpan(spellCheckSpan);
184+
break;
200185
}
201186
}
202187
}
203188
}
204189

205-
private static String[] getSuggestions(SuggestionsInfo suggestionsInfo) {
206-
// A negative suggestion count is possible
207-
final int len = Math.max(0, suggestionsInfo.getSuggestionsCount());
208-
String[] suggestions = new String[len];
209-
for (int j = 0; j < len; j++) {
210-
suggestions[j] = suggestionsInfo.getSuggestionAt(j);
190+
private void createMisspelledSuggestionSpan(SuggestionsInfo suggestionsInfo,
191+
SpellCheckSpan spellCheckSpan) {
192+
final Editable editable = (Editable) mTextView.getText();
193+
final int start = editable.getSpanStart(spellCheckSpan);
194+
final int end = editable.getSpanEnd(spellCheckSpan);
195+
196+
// Other suggestion spans may exist on that region, with identical suggestions, filter
197+
// them out to avoid duplicates. First, filter suggestion spans on that exact region.
198+
SuggestionSpan[] suggestionSpans = editable.getSpans(start, end, SuggestionSpan.class);
199+
final int length = suggestionSpans.length;
200+
for (int i = 0; i < length; i++) {
201+
final int spanStart = editable.getSpanStart(suggestionSpans[i]);
202+
final int spanEnd = editable.getSpanEnd(suggestionSpans[i]);
203+
if (spanStart != start || spanEnd != end) {
204+
suggestionSpans[i] = null;
205+
break;
206+
}
207+
}
208+
209+
final int suggestionsCount = suggestionsInfo.getSuggestionsCount();
210+
String[] suggestions;
211+
if (suggestionsCount <= 0) {
212+
// A negative suggestion count is possible
213+
suggestions = ArrayUtils.emptyArray(String.class);
214+
} else {
215+
int numberOfSuggestions = 0;
216+
suggestions = new String[suggestionsCount];
217+
218+
for (int i = 0; i < suggestionsCount; i++) {
219+
final String spellSuggestion = suggestionsInfo.getSuggestionAt(i);
220+
if (spellSuggestion == null) break;
221+
boolean suggestionFound = false;
222+
223+
for (int j = 0; j < length && !suggestionFound; j++) {
224+
if (suggestionSpans[j] == null) break;
225+
226+
String[] suggests = suggestionSpans[j].getSuggestions();
227+
for (int k = 0; k < suggests.length; k++) {
228+
if (spellSuggestion.equals(suggests[k])) {
229+
// The suggestion is already provided by an other SuggestionSpan
230+
suggestionFound = true;
231+
break;
232+
}
233+
}
234+
}
235+
236+
if (!suggestionFound) {
237+
suggestions[numberOfSuggestions++] = spellSuggestion;
238+
}
239+
}
240+
241+
if (numberOfSuggestions != suggestionsCount) {
242+
String[] newSuggestions = new String[numberOfSuggestions];
243+
System.arraycopy(suggestions, 0, newSuggestions, 0, numberOfSuggestions);
244+
suggestions = newSuggestions;
245+
}
211246
}
212-
return suggestions;
247+
248+
SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions,
249+
SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
250+
editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
251+
252+
// TODO limit to the word rectangle region
253+
mTextView.invalidate();
254+
editable.removeSpan(spellCheckSpan);
213255
}
214256
}

core/java/android/widget/TextView.java

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ class InputMethodState {
353353
// Set when this TextView gained focus with some text selected. Will start selection mode.
354354
private boolean mCreatedWithASelection = false;
355355

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

358360
private SpellChecker mSpellChecker;
@@ -2937,11 +2939,19 @@ public Parcelable onSaveInstanceState() {
29372939

29382940
Spannable sp = new SpannableString(mText);
29392941

2940-
for (ChangeWatcher cw :
2941-
sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
2942+
for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
29422943
sp.removeSpan(cw);
29432944
}
29442945

2946+
SuggestionSpan[] suggestionSpans = sp.getSpans(0, sp.length(), SuggestionSpan.class);
2947+
for (int i = 0; i < suggestionSpans.length; i++) {
2948+
int flags = suggestionSpans[i].getFlags();
2949+
if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
2950+
&& (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
2951+
sp.removeSpan(suggestionSpans[i]);
2952+
}
2953+
}
2954+
29452955
sp.removeSpan(mSuggestionRangeSpan);
29462956

29472957
ss.text = sp;
@@ -4449,7 +4459,6 @@ protected void onDetachedFromWindow() {
44494459

44504460
if (mSpellChecker != null) {
44514461
mSpellChecker.closeSession();
4452-
removeMisspelledSpans();
44534462
// Forces the creation of a new SpellChecker next time this window is created.
44544463
// Will handle the cases where the settings has been changed in the meantime.
44554464
mSpellChecker = null;
@@ -8461,24 +8470,6 @@ private void downgradeEasyCorrectionSpans() {
84618470
}
84628471
}
84638472

8464-
/**
8465-
* Removes the suggestion spans for misspelled words.
8466-
*/
8467-
private void removeMisspelledSpans() {
8468-
if (mText instanceof Spannable) {
8469-
Spannable spannable = (Spannable) mText;
8470-
SuggestionSpan[] suggestionSpans = spannable.getSpans(0,
8471-
spannable.length(), SuggestionSpan.class);
8472-
for (int i = 0; i < suggestionSpans.length; i++) {
8473-
int flags = suggestionSpans[i].getFlags();
8474-
if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
8475-
&& (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
8476-
spannable.removeSpan(suggestionSpans[i]);
8477-
}
8478-
}
8479-
}
8480-
}
8481-
84828473
@Override
84838474
public boolean onGenericMotionEvent(MotionEvent event) {
84848475
if (mMovement != null && mText instanceof Spannable && mLayout != null) {
@@ -8959,9 +8950,8 @@ int prepareWordIterator(int start, int end) {
89598950
mWordIterator = new WordIterator();
89608951
}
89618952

8962-
final int TEXT_WINDOW_WIDTH = 50; // Should be larger than the longest word's length
8963-
final int windowStart = Math.max(0, start - TEXT_WINDOW_WIDTH);
8964-
final int windowEnd = Math.min(mText.length(), end + TEXT_WINDOW_WIDTH);
8953+
final int windowStart = Math.max(0, start - WORD_ITERATOR_WINDOW_WIDTH);
8954+
final int windowEnd = Math.min(mText.length(), end + WORD_ITERATOR_WINDOW_WIDTH);
89658955
mWordIterator.setCharSequence(mText.subSequence(windowStart, windowEnd));
89668956

89678957
return windowStart;

0 commit comments

Comments
 (0)