Skip to content

Commit 50956b2

Browse files
satok16Android (Google) Code Review
authored andcommitted
Merge "DO NOT MERGE. Backport If36b8a69e7fa22e837c99d Fix the issue that the spell check doesn't start when the user changes the cursor position by touch" into jb-dev
2 parents dad52e1 + 8589474 commit 50956b2

File tree

1 file changed

+110
-34
lines changed

1 file changed

+110
-34
lines changed

core/java/android/widget/SpellChecker.java

Lines changed: 110 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
import android.text.Selection;
2222
import android.text.SpannableStringBuilder;
2323
import android.text.Spanned;
24+
import android.text.TextUtils;
2425
import android.text.method.WordIterator;
2526
import android.text.style.SpellCheckSpan;
2627
import android.text.style.SuggestionSpan;
2728
import android.util.Log;
29+
import android.util.LruCache;
2830
import android.view.textservice.SentenceSuggestionsInfo;
2931
import android.view.textservice.SpellCheckerSession;
3032
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
@@ -95,6 +97,10 @@ public class SpellChecker implements SpellCheckerSessionListener {
9597

9698
private Runnable mSpellRunnable;
9799

100+
private static final int SUGGESTION_SPAN_CACHE_SIZE = 10;
101+
private final LruCache<Long, SuggestionSpan> mSuggestionSpanCache =
102+
new LruCache<Long, SuggestionSpan>(SUGGESTION_SPAN_CACHE_SIZE);
103+
98104
public SpellChecker(TextView textView) {
99105
mTextView = textView;
100106

@@ -126,13 +132,15 @@ private void resetSession() {
126132

127133
// Restore SpellCheckSpans in pool
128134
for (int i = 0; i < mLength; i++) {
135+
// Resets id and progress to invalidate spell check span
129136
mSpellCheckSpans[i].setSpellCheckInProgress(false);
130137
mIds[i] = -1;
131138
}
132139
mLength = 0;
133140

134141
// Remove existing misspelled SuggestionSpans
135142
mTextView.removeMisspelledSpans((Editable) mTextView.getText());
143+
mSuggestionSpanCache.evictAll();
136144
}
137145

138146
private void setLocale(Locale locale) {
@@ -199,6 +207,7 @@ private void addSpellCheckSpan(Editable editable, int start, int end) {
199207
public void removeSpellCheckSpan(SpellCheckSpan spellCheckSpan) {
200208
for (int i = 0; i < mLength; i++) {
201209
if (mSpellCheckSpans[i] == spellCheckSpan) {
210+
// Resets id and progress to invalidate spell check span
202211
mSpellCheckSpans[i].setSpellCheckInProgress(false);
203212
mIds[i] = -1;
204213
return;
@@ -211,6 +220,9 @@ public void onSelectionChanged() {
211220
}
212221

213222
public void spellCheck(int start, int end) {
223+
if (DBG) {
224+
Log.d(TAG, "Start spell-checking: " + start + ", " + end);
225+
}
214226
final Locale locale = mTextView.getTextServicesLocale();
215227
final boolean isSessionActive = isSessionActive();
216228
if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
@@ -238,6 +250,9 @@ public void spellCheck(int start, int end) {
238250
}
239251
}
240252

253+
if (DBG) {
254+
Log.d(TAG, "new spell parser.");
255+
}
241256
// No available parser found in pool, create a new one
242257
SpellParser[] newSpellParsers = new SpellParser[length + 1];
243258
System.arraycopy(mSpellParsers, 0, newSpellParsers, 0, length);
@@ -260,13 +275,22 @@ private void spellCheck() {
260275

261276
for (int i = 0; i < mLength; i++) {
262277
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
263-
if (spellCheckSpan.isSpellCheckInProgress()) continue;
278+
if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) continue;
264279

265280
final int start = editable.getSpanStart(spellCheckSpan);
266281
final int end = editable.getSpanEnd(spellCheckSpan);
267282

268283
// Do not check this word if the user is currently editing it
269-
if (start >= 0 && end > start && (selectionEnd < start || selectionStart > end)) {
284+
final boolean isEditing;
285+
if (mIsSentenceSpellCheckSupported) {
286+
// Allow the overlap of the cursor and the first boundary of the spell check span
287+
// no to skip the spell check of the following word because the
288+
// following word will never be spell-checked even if the user finishes composing
289+
isEditing = selectionEnd <= start || selectionStart > end;
290+
} else {
291+
isEditing = selectionEnd < start || selectionStart > end;
292+
}
293+
if (start >= 0 && end > start && isEditing) {
270294
final String word = (editable instanceof SpannableStringBuilder) ?
271295
((SpannableStringBuilder) editable).substring(start, end) :
272296
editable.subSequence(start, end).toString();
@@ -372,6 +396,9 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
372396
}
373397

374398
private void scheduleNewSpellCheck() {
399+
if (DBG) {
400+
Log.i(TAG, "schedule new spell check.");
401+
}
375402
if (mSpellRunnable == null) {
376403
mSpellRunnable = new Runnable() {
377404
@Override
@@ -423,6 +450,20 @@ private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo s
423450

424451
SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions,
425452
SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
453+
// TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface
454+
// to share the logic of word level spell checker and sentence level spell checker
455+
if (mIsSentenceSpellCheckSupported) {
456+
final long key = TextUtils.packRangeInLong(start, end);
457+
final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key);
458+
if (tempSuggestionSpan != null) {
459+
if (DBG) {
460+
Log.i(TAG, "Cached span on the same position is cleard. "
461+
+ editable.subSequence(start, end));
462+
}
463+
editable.removeSpan(tempSuggestionSpan);
464+
}
465+
mSuggestionSpanCache.put(key, suggestionSpan);
466+
}
426467
editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
427468

428469
mTextView.invalidateRegion(start, end, false /* No cursor involved */);
@@ -447,10 +488,17 @@ public void stop() {
447488
}
448489

449490
private void setRangeSpan(Editable editable, int start, int end) {
491+
if (DBG) {
492+
Log.d(TAG, "set next range span: " + start + ", " + end);
493+
}
450494
editable.setSpan(mRange, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
451495
}
452496

453497
private void removeRangeSpan(Editable editable) {
498+
if (DBG) {
499+
Log.d(TAG, "Remove range span." + editable.getSpanStart(editable)
500+
+ editable.getSpanEnd(editable));
501+
}
454502
editable.removeSpan(mRange);
455503
}
456504

@@ -484,6 +532,9 @@ public void parse() {
484532
wordEnd = mWordIterator.getEnd(wordStart);
485533
}
486534
if (wordEnd == BreakIterator.DONE) {
535+
if (DBG) {
536+
Log.i(TAG, "No more spell check.");
537+
}
487538
removeRangeSpan(editable);
488539
return;
489540
}
@@ -499,47 +550,72 @@ public void parse() {
499550
boolean scheduleOtherSpellCheck = false;
500551

501552
if (mIsSentenceSpellCheckSupported) {
502-
int regionEnd;
503553
if (wordIteratorWindowEnd < end) {
554+
if (DBG) {
555+
Log.i(TAG, "schedule other spell check.");
556+
}
504557
// Several batches needed on that region. Cut after last previous word
505-
regionEnd = mWordIterator.preceding(wordIteratorWindowEnd);
506558
scheduleOtherSpellCheck = true;
507-
} else {
508-
regionEnd = mWordIterator.preceding(end);
509559
}
510-
boolean correct = regionEnd != BreakIterator.DONE;
560+
int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
561+
boolean correct = spellCheckEnd != BreakIterator.DONE;
511562
if (correct) {
512-
regionEnd = mWordIterator.getEnd(regionEnd);
513-
correct = regionEnd != BreakIterator.DONE;
563+
spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
564+
correct = spellCheckEnd != BreakIterator.DONE;
514565
}
515566
if (!correct) {
516-
editable.removeSpan(mRange);
517-
return;
518-
}
519-
// Stop spell checking when there are no characters in the range.
520-
if (wordEnd < start) {
521-
return;
522-
}
523-
// TODO: Find the start position of the sentence.
524-
final int spellCheckStart = wordStart;
525-
if (regionEnd <= spellCheckStart) {
567+
if (DBG) {
568+
Log.i(TAG, "Incorrect range span.");
569+
}
570+
removeRangeSpan(editable);
526571
return;
527572
}
528-
final int selectionStart = Selection.getSelectionStart(editable);
529-
final int selectionEnd = Selection.getSelectionEnd(editable);
530-
if (DBG) {
531-
Log.d(TAG, "addSpellCheckSpan: "
532-
+ editable.subSequence(spellCheckStart, regionEnd)
533-
+ ", regionEnd = " + regionEnd + ", spellCheckStart = "
534-
+ spellCheckStart + ", sel start = " + selectionStart + ", sel end ="
535-
+ selectionEnd);
536-
}
537-
// Do not check this word if the user is currently editing it
538-
if (spellCheckStart >= 0 && regionEnd > spellCheckStart
539-
&& (selectionEnd < spellCheckStart || selectionStart > regionEnd)) {
540-
addSpellCheckSpan(editable, spellCheckStart, regionEnd);
541-
}
542-
wordStart = regionEnd;
573+
do {
574+
// TODO: Find the start position of the sentence.
575+
int spellCheckStart = wordStart;
576+
boolean createSpellCheckSpan = true;
577+
// Cancel or merge overlapped spell check spans
578+
for (int i = 0; i < mLength; ++i) {
579+
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
580+
if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) {
581+
continue;
582+
}
583+
final int spanStart = editable.getSpanStart(spellCheckSpan);
584+
final int spanEnd = editable.getSpanEnd(spellCheckSpan);
585+
if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) {
586+
// No need to merge
587+
continue;
588+
}
589+
if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) {
590+
// There is a completely overlapped spell check span
591+
// skip this span
592+
createSpellCheckSpan = false;
593+
if (DBG) {
594+
Log.i(TAG, "The range is overrapped. Skip spell check.");
595+
}
596+
break;
597+
}
598+
removeSpellCheckSpan(spellCheckSpan);
599+
spellCheckStart = Math.min(spanStart, spellCheckStart);
600+
spellCheckEnd = Math.max(spanEnd, spellCheckEnd);
601+
}
602+
603+
if (DBG) {
604+
Log.d(TAG, "addSpellCheckSpan: "
605+
+ ", End = " + spellCheckEnd + ", Start = " + spellCheckStart
606+
+ ", next = " + scheduleOtherSpellCheck + "\n"
607+
+ editable.subSequence(spellCheckStart, spellCheckEnd));
608+
}
609+
610+
// Stop spell checking when there are no characters in the range.
611+
if (spellCheckEnd < start) {
612+
break;
613+
}
614+
if (createSpellCheckSpan) {
615+
addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
616+
}
617+
} while (false);
618+
wordStart = spellCheckEnd;
543619
} else {
544620
while (wordStart <= end) {
545621
if (wordEnd >= start && wordEnd > wordStart) {

0 commit comments

Comments
 (0)