Skip to content

Commit b062e81

Browse files
author
Gilles Debunne
committed
Too many SpellCheckSpans are created.
Removed the Runnable in SpellChecker, spell check is triggered at the end of updateSpellCheckSpans instead of when a new SpellCheckSpan is created. Cache the spans in updateSpellCheckSpans to limit the calls to getSpans. When typing, every new letter in a word will create a SpellCheckSpan (this is needed in case the user taps somewhere else on the screen) The SpellCheckSpans are pooled in SpellChecker to limit unnecessary new SpellCheckSpan creation. Minor optimization on test order in getSpans to avoid some calculation. Spell check is not started everytime the selection is changed (would be triggered when the insertion handle is moved). Explicitely do that only on tap. Change-Id: Ibacf80dd4ba098494e0b5ba0e58a362782fc8f71
1 parent 4c08848 commit b062e81

File tree

4 files changed

+90
-116
lines changed

4 files changed

+90
-116
lines changed

core/java/android/text/SpannableStringBuilder.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -710,18 +710,17 @@ public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
710710

711711
for (int i = 0; i < spanCount; i++) {
712712
int spanStart = starts[i];
713-
int spanEnd = ends[i];
714-
715713
if (spanStart > gapstart) {
716714
spanStart -= gaplen;
717715
}
718-
if (spanEnd > gapstart) {
719-
spanEnd -= gaplen;
720-
}
721-
722716
if (spanStart > queryEnd) {
723717
continue;
724718
}
719+
720+
int spanEnd = ends[i];
721+
if (spanEnd > gapstart) {
722+
spanEnd -= gaplen;
723+
}
725724
if (spanEnd < queryStart) {
726725
continue;
727726
}

core/java/android/text/style/SpellCheckSpan.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public SpellCheckSpan(Parcel src) {
3939
mSpellCheckInProgress = (src.readInt() != 0);
4040
}
4141

42-
public void setSpellCheckInProgress() {
43-
mSpellCheckInProgress = true;
42+
public void setSpellCheckInProgress(boolean inProgress) {
43+
mSpellCheckInProgress = inProgress;
4444
}
4545

4646
public boolean isSpellCheckInProgress() {

core/java/android/widget/SpellChecker.java

Lines changed: 34 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import android.text.Spanned;
2323
import android.text.style.SpellCheckSpan;
2424
import android.text.style.SuggestionSpan;
25-
import android.util.Log;
2625
import android.view.textservice.SpellCheckerSession;
2726
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
2827
import android.view.textservice.SuggestionsInfo;
@@ -40,23 +39,21 @@
4039
* @hide
4140
*/
4241
public class SpellChecker implements SpellCheckerSessionListener {
43-
private static final String LOG_TAG = "SpellChecker";
44-
private static final boolean DEBUG_SPELL_CHECK = false;
45-
private static final int DELAY_BEFORE_SPELL_CHECK = 400; // milliseconds
4642

4743
private final TextView mTextView;
4844

4945
final SpellCheckerSession mSpellCheckerSession;
5046
final int mCookie;
5147

52-
// Paired arrays for the (id, spellCheckSpan) pair. mIndex is the next available position
48+
// Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
49+
// SpellCheckSpan has been recycled and can be-reused.
50+
// May contain null SpellCheckSpans after a given index.
5351
private int[] mIds;
5452
private SpellCheckSpan[] mSpellCheckSpans;
55-
// The actual current number of used slots in the above arrays
53+
// The mLength first elements of the above arrays have been initialized
5654
private int mLength;
5755

5856
private int mSpanSequenceCounter = 0;
59-
private Runnable mChecker;
6057

6158
public SpellChecker(TextView textView) {
6259
mTextView = textView;
@@ -69,7 +66,7 @@ public SpellChecker(TextView textView) {
6966
mCookie = hashCode();
7067

7168
// Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand
72-
final int size = ArrayUtils.idealObjectArraySize(4);
69+
final int size = ArrayUtils.idealObjectArraySize(1);
7370
mIds = new int[size];
7471
mSpellCheckSpans = new SpellCheckSpan[size];
7572
mLength = 0;
@@ -89,73 +86,50 @@ public void closeSession() {
8986
}
9087
}
9188

92-
public void addSpellCheckSpan(SpellCheckSpan spellCheckSpan) {
93-
int length = mIds.length;
94-
if (mLength >= length) {
95-
final int newSize = length * 2;
89+
private int nextSpellCheckSpanIndex() {
90+
for (int i = 0; i < mLength; i++) {
91+
if (mIds[i] < 0) return i;
92+
}
93+
94+
if (mLength == mSpellCheckSpans.length) {
95+
final int newSize = mLength * 2;
9696
int[] newIds = new int[newSize];
9797
SpellCheckSpan[] newSpellCheckSpans = new SpellCheckSpan[newSize];
98-
System.arraycopy(mIds, 0, newIds, 0, length);
99-
System.arraycopy(mSpellCheckSpans, 0, newSpellCheckSpans, 0, length);
98+
System.arraycopy(mIds, 0, newIds, 0, mLength);
99+
System.arraycopy(mSpellCheckSpans, 0, newSpellCheckSpans, 0, mLength);
100100
mIds = newIds;
101101
mSpellCheckSpans = newSpellCheckSpans;
102102
}
103103

104-
mIds[mLength] = mSpanSequenceCounter++;
105-
mSpellCheckSpans[mLength] = spellCheckSpan;
104+
mSpellCheckSpans[mLength] = new SpellCheckSpan();
106105
mLength++;
106+
return mLength - 1;
107+
}
107108

108-
if (DEBUG_SPELL_CHECK) {
109-
final Editable mText = (Editable) mTextView.getText();
110-
int start = mText.getSpanStart(spellCheckSpan);
111-
int end = mText.getSpanEnd(spellCheckSpan);
112-
if (start >= 0 && end >= 0) {
113-
Log.d(LOG_TAG, "Schedule check " + mText.subSequence(start, end));
114-
} else {
115-
Log.d(LOG_TAG, "Schedule check EMPTY!");
116-
}
117-
}
118-
119-
scheduleSpellCheck();
109+
public void addSpellCheckSpan(int wordStart, int wordEnd) {
110+
final int index = nextSpellCheckSpanIndex();
111+
((Editable) mTextView.getText()).setSpan(mSpellCheckSpans[index], wordStart, wordEnd,
112+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
113+
mIds[index] = mSpanSequenceCounter++;
120114
}
121115

122116
public void removeSpellCheckSpan(SpellCheckSpan spellCheckSpan) {
123117
for (int i = 0; i < mLength; i++) {
124118
if (mSpellCheckSpans[i] == spellCheckSpan) {
125-
removeAtIndex(i);
119+
mSpellCheckSpans[i].setSpellCheckInProgress(false);
120+
mIds[i] = -1;
126121
return;
127122
}
128123
}
129124
}
130125

131-
private void removeAtIndex(int i) {
132-
System.arraycopy(mIds, i + 1, mIds, i, mLength - i - 1);
133-
System.arraycopy(mSpellCheckSpans, i + 1, mSpellCheckSpans, i, mLength - i - 1);
134-
mLength--;
135-
}
136-
137126
public void onSelectionChanged() {
138-
scheduleSpellCheck();
127+
spellCheck();
139128
}
140129

141-
private void scheduleSpellCheck() {
142-
if (mLength == 0) return;
130+
public void spellCheck() {
143131
if (mSpellCheckerSession == null) return;
144132

145-
if (mChecker != null) {
146-
mTextView.removeCallbacks(mChecker);
147-
}
148-
if (mChecker == null) {
149-
mChecker = new Runnable() {
150-
public void run() {
151-
spellCheck();
152-
}
153-
};
154-
}
155-
mTextView.postDelayed(mChecker, DELAY_BEFORE_SPELL_CHECK);
156-
}
157-
158-
private void spellCheck() {
159133
final Editable editable = (Editable) mTextView.getText();
160134
final int selectionStart = Selection.getSelectionStart(editable);
161135
final int selectionEnd = Selection.getSelectionEnd(editable);
@@ -164,8 +138,7 @@ private void spellCheck() {
164138
int textInfosCount = 0;
165139

166140
for (int i = 0; i < mLength; i++) {
167-
SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
168-
141+
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
169142
if (spellCheckSpan.isSpellCheckInProgress()) continue;
170143

171144
final int start = editable.getSpanStart(spellCheckSpan);
@@ -174,7 +147,7 @@ private void spellCheck() {
174147
// Do not check this word if the user is currently editing it
175148
if (start >= 0 && end > start && (selectionEnd < start || selectionStart > end)) {
176149
final String word = editable.subSequence(start, end).toString();
177-
spellCheckSpan.setSpellCheckInProgress();
150+
spellCheckSpan.setSpellCheckInProgress(true);
178151
textInfos[textInfosCount++] = new TextInfo(word, mCookie, mIds[i]);
179152
}
180153
}
@@ -196,27 +169,18 @@ public void onGetSuggestions(SuggestionsInfo[] results) {
196169
for (int i = 0; i < results.length; i++) {
197170
SuggestionsInfo suggestionsInfo = results[i];
198171
if (suggestionsInfo.getCookie() != mCookie) continue;
199-
200172
final int sequenceNumber = suggestionsInfo.getSequence();
201-
// Starting from the end, to limit the number of array copy while removing
202-
for (int j = mLength - 1; j >= 0; j--) {
173+
174+
for (int j = 0; j < mLength; j++) {
175+
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
176+
203177
if (sequenceNumber == mIds[j]) {
204-
SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
205178
final int attributes = suggestionsInfo.getSuggestionsAttributes();
206179
boolean isInDictionary =
207180
((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0);
208181
boolean looksLikeTypo =
209182
((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);
210183

211-
if (DEBUG_SPELL_CHECK) {
212-
final int start = editable.getSpanStart(spellCheckSpan);
213-
final int end = editable.getSpanEnd(spellCheckSpan);
214-
Log.d(LOG_TAG, "Result sequence=" + suggestionsInfo.getSequence() + " " +
215-
editable.subSequence(start, end) +
216-
"\t" + (isInDictionary?"IN_DICT" : "NOT_DICT") +
217-
"\t" + (looksLikeTypo?"TYPO" : "NOT_TYPO"));
218-
}
219-
220184
if (!isInDictionary && looksLikeTypo) {
221185
String[] suggestions = getSuggestions(suggestionsInfo);
222186
if (suggestions.length > 0) {
@@ -230,13 +194,6 @@ public void onGetSuggestions(SuggestionsInfo[] results) {
230194
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
231195
// TODO limit to the word rectangle region
232196
mTextView.invalidate();
233-
234-
if (DEBUG_SPELL_CHECK) {
235-
String suggestionsString = "";
236-
for (String s : suggestions) { suggestionsString += s + "|"; }
237-
Log.d(LOG_TAG, " Suggestions for " + sequenceNumber + " " +
238-
editable.subSequence(start, end)+ " " + suggestionsString);
239-
}
240197
}
241198
}
242199
editable.removeSpan(spellCheckSpan);
@@ -246,9 +203,10 @@ public void onGetSuggestions(SuggestionsInfo[] results) {
246203
}
247204

248205
private static String[] getSuggestions(SuggestionsInfo suggestionsInfo) {
206+
// A negative suggestion count is possible
249207
final int len = Math.max(0, suggestionsInfo.getSuggestionsCount());
250208
String[] suggestions = new String[len];
251-
for (int j = 0; j < len; ++j) {
209+
for (int j = 0; j < len; j++) {
252210
suggestions[j] = suggestionsInfo.getSuggestionAt(j);
253211
}
254212
return suggestions;

0 commit comments

Comments
 (0)