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
This commit is contained in:
Gilles Debunne
2011-09-29 16:37:27 -07:00
parent eba3d92341
commit 176cd0d3b4
2 changed files with 80 additions and 48 deletions

View File

@ -30,8 +30,6 @@ import android.view.textservice.TextServicesManager;
import com.android.internal.util.ArrayUtils;
import java.util.Locale;
/**
* Helper class for TextView. Bridge between the TextView and the Dictionnary service.
@ -167,15 +165,12 @@ public class SpellChecker implements SpellCheckerSessionListener {
@Override
public void onGetSuggestions(SuggestionsInfo[] results) {
final Editable editable = (Editable) mTextView.getText();
for (int i = 0; i < results.length; i++) {
SuggestionsInfo suggestionsInfo = results[i];
if (suggestionsInfo.getCookie() != mCookie) continue;
final int sequenceNumber = suggestionsInfo.getSequence();
for (int j = 0; j < mLength; j++) {
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
if (sequenceNumber == mIds[j]) {
final int attributes = suggestionsInfo.getSuggestionsAttributes();
boolean isInDictionary =
@ -184,31 +179,78 @@ public class SpellChecker implements SpellCheckerSessionListener {
((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);
if (!isInDictionary && looksLikeTypo) {
String[] suggestions = getSuggestions(suggestionsInfo);
SuggestionSpan suggestionSpan = new SuggestionSpan(
mTextView.getContext(), suggestions,
SuggestionSpan.FLAG_EASY_CORRECT |
SuggestionSpan.FLAG_MISSPELLED);
final int start = editable.getSpanStart(spellCheckSpan);
final int end = editable.getSpanEnd(spellCheckSpan);
editable.setSpan(suggestionSpan, start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// TODO limit to the word rectangle region
mTextView.invalidate();
createMisspelledSuggestionSpan(suggestionsInfo, mSpellCheckSpans[j]);
}
editable.removeSpan(spellCheckSpan);
break;
}
}
}
}
private static String[] getSuggestions(SuggestionsInfo suggestionsInfo) {
// A negative suggestion count is possible
final int len = Math.max(0, suggestionsInfo.getSuggestionsCount());
String[] suggestions = new String[len];
for (int j = 0; j < len; j++) {
suggestions[j] = suggestionsInfo.getSuggestionAt(j);
private void createMisspelledSuggestionSpan(SuggestionsInfo suggestionsInfo,
SpellCheckSpan spellCheckSpan) {
final Editable editable = (Editable) mTextView.getText();
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 = editable.getSpans(start, end, SuggestionSpan.class);
final int length = suggestionSpans.length;
for (int i = 0; i < length; i++) {
final int spanStart = editable.getSpanStart(suggestionSpans[i]);
final int spanEnd = editable.getSpanEnd(suggestionSpans[i]);
if (spanStart != start || spanEnd != end) {
suggestionSpans[i] = null;
break;
}
}
return suggestions;
final int suggestionsCount = suggestionsInfo.getSuggestionsCount();
String[] suggestions;
if (suggestionsCount <= 0) {
// A negative suggestion count is possible
suggestions = ArrayUtils.emptyArray(String.class);
} else {
int numberOfSuggestions = 0;
suggestions = new String[suggestionsCount];
for (int i = 0; i < suggestionsCount; i++) {
final String spellSuggestion = suggestionsInfo.getSuggestionAt(i);
if (spellSuggestion == null) break;
boolean suggestionFound = false;
for (int j = 0; j < length && !suggestionFound; j++) {
if (suggestionSpans[j] == null) break;
String[] suggests = suggestionSpans[j].getSuggestions();
for (int k = 0; k < suggests.length; k++) {
if (spellSuggestion.equals(suggests[k])) {
// The suggestion is already provided by an other SuggestionSpan
suggestionFound = true;
break;
}
}
}
if (!suggestionFound) {
suggestions[numberOfSuggestions++] = spellSuggestion;
}
}
if (numberOfSuggestions != suggestionsCount) {
String[] newSuggestions = new String[numberOfSuggestions];
System.arraycopy(suggestions, 0, newSuggestions, 0, numberOfSuggestions);
suggestions = newSuggestions;
}
}
SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions,
SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// TODO limit to the word rectangle region
mTextView.invalidate();
editable.removeSpan(spellCheckSpan);
}
}

View File

@ -353,6 +353,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Set when this TextView gained focus with some text selected. Will start selection mode.
private boolean mCreatedWithASelection = false;
// Size of the window for the word iterator, should be greater than the longest word's length
private static final int WORD_ITERATOR_WINDOW_WIDTH = 50;
private WordIterator mWordIterator;
private SpellChecker mSpellChecker;
@ -2937,11 +2939,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Spannable sp = new SpannableString(mText);
for (ChangeWatcher cw :
sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
sp.removeSpan(cw);
}
SuggestionSpan[] suggestionSpans = sp.getSpans(0, sp.length(), SuggestionSpan.class);
for (int i = 0; i < suggestionSpans.length; i++) {
int flags = suggestionSpans[i].getFlags();
if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
&& (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
sp.removeSpan(suggestionSpans[i]);
}
}
sp.removeSpan(mSuggestionRangeSpan);
ss.text = sp;
@ -4449,7 +4459,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mSpellChecker != null) {
mSpellChecker.closeSession();
removeMisspelledSpans();
// Forces the creation of a new SpellChecker next time this window is created.
// Will handle the cases where the settings has been changed in the meantime.
mSpellChecker = null;
@ -8461,24 +8470,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
/**
* Removes the suggestion spans for misspelled words.
*/
private void removeMisspelledSpans() {
if (mText instanceof Spannable) {
Spannable spannable = (Spannable) mText;
SuggestionSpan[] suggestionSpans = spannable.getSpans(0,
spannable.length(), SuggestionSpan.class);
for (int i = 0; i < suggestionSpans.length; i++) {
int flags = suggestionSpans[i].getFlags();
if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
&& (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
spannable.removeSpan(suggestionSpans[i]);
}
}
}
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (mMovement != null && mText instanceof Spannable && mLayout != null) {
@ -8959,9 +8950,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mWordIterator = new WordIterator();
}
final int TEXT_WINDOW_WIDTH = 50; // Should be larger than the longest word's length
final int windowStart = Math.max(0, start - TEXT_WINDOW_WIDTH);
final int windowEnd = Math.min(mText.length(), end + TEXT_WINDOW_WIDTH);
final int windowStart = Math.max(0, start - WORD_ITERATOR_WINDOW_WIDTH);
final int windowEnd = Math.min(mText.length(), end + WORD_ITERATOR_WINDOW_WIDTH);
mWordIterator.setCharSequence(mText.subSequence(windowStart, windowEnd));
return windowStart;