Skip to content

Commit f4314df

Browse files
Gilles DebunneAndroid (Google) Code Review
authored andcommitted
Merge "Too many SpellCheckSpans are created."
2 parents 2452ad3 + b062e81 commit f4314df

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)