Merge "Too many SpellCheckSpans are created."
This commit is contained in:
committed by
Android (Google) Code Review
commit
f4314dffbd
@@ -710,18 +710,17 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
|
||||
|
||||
for (int i = 0; i < spanCount; i++) {
|
||||
int spanStart = starts[i];
|
||||
int spanEnd = ends[i];
|
||||
|
||||
if (spanStart > gapstart) {
|
||||
spanStart -= gaplen;
|
||||
}
|
||||
if (spanEnd > gapstart) {
|
||||
spanEnd -= gaplen;
|
||||
}
|
||||
|
||||
if (spanStart > queryEnd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int spanEnd = ends[i];
|
||||
if (spanEnd > gapstart) {
|
||||
spanEnd -= gaplen;
|
||||
}
|
||||
if (spanEnd < queryStart) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ public class SpellCheckSpan implements ParcelableSpan {
|
||||
mSpellCheckInProgress = (src.readInt() != 0);
|
||||
}
|
||||
|
||||
public void setSpellCheckInProgress() {
|
||||
mSpellCheckInProgress = true;
|
||||
public void setSpellCheckInProgress(boolean inProgress) {
|
||||
mSpellCheckInProgress = inProgress;
|
||||
}
|
||||
|
||||
public boolean isSpellCheckInProgress() {
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.text.Selection;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.SpellCheckSpan;
|
||||
import android.text.style.SuggestionSpan;
|
||||
import android.util.Log;
|
||||
import android.view.textservice.SpellCheckerSession;
|
||||
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
|
||||
import android.view.textservice.SuggestionsInfo;
|
||||
@@ -40,23 +39,21 @@ import java.util.Locale;
|
||||
* @hide
|
||||
*/
|
||||
public class SpellChecker implements SpellCheckerSessionListener {
|
||||
private static final String LOG_TAG = "SpellChecker";
|
||||
private static final boolean DEBUG_SPELL_CHECK = false;
|
||||
private static final int DELAY_BEFORE_SPELL_CHECK = 400; // milliseconds
|
||||
|
||||
private final TextView mTextView;
|
||||
|
||||
final SpellCheckerSession mSpellCheckerSession;
|
||||
final int mCookie;
|
||||
|
||||
// Paired arrays for the (id, spellCheckSpan) pair. mIndex is the next available position
|
||||
// Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
|
||||
// SpellCheckSpan has been recycled and can be-reused.
|
||||
// May contain null SpellCheckSpans after a given index.
|
||||
private int[] mIds;
|
||||
private SpellCheckSpan[] mSpellCheckSpans;
|
||||
// The actual current number of used slots in the above arrays
|
||||
// The mLength first elements of the above arrays have been initialized
|
||||
private int mLength;
|
||||
|
||||
private int mSpanSequenceCounter = 0;
|
||||
private Runnable mChecker;
|
||||
|
||||
public SpellChecker(TextView textView) {
|
||||
mTextView = textView;
|
||||
@@ -69,7 +66,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
||||
mCookie = hashCode();
|
||||
|
||||
// Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand
|
||||
final int size = ArrayUtils.idealObjectArraySize(4);
|
||||
final int size = ArrayUtils.idealObjectArraySize(1);
|
||||
mIds = new int[size];
|
||||
mSpellCheckSpans = new SpellCheckSpan[size];
|
||||
mLength = 0;
|
||||
@@ -89,73 +86,50 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
||||
}
|
||||
}
|
||||
|
||||
public void addSpellCheckSpan(SpellCheckSpan spellCheckSpan) {
|
||||
int length = mIds.length;
|
||||
if (mLength >= length) {
|
||||
final int newSize = length * 2;
|
||||
private int nextSpellCheckSpanIndex() {
|
||||
for (int i = 0; i < mLength; i++) {
|
||||
if (mIds[i] < 0) return i;
|
||||
}
|
||||
|
||||
if (mLength == mSpellCheckSpans.length) {
|
||||
final int newSize = mLength * 2;
|
||||
int[] newIds = new int[newSize];
|
||||
SpellCheckSpan[] newSpellCheckSpans = new SpellCheckSpan[newSize];
|
||||
System.arraycopy(mIds, 0, newIds, 0, length);
|
||||
System.arraycopy(mSpellCheckSpans, 0, newSpellCheckSpans, 0, length);
|
||||
System.arraycopy(mIds, 0, newIds, 0, mLength);
|
||||
System.arraycopy(mSpellCheckSpans, 0, newSpellCheckSpans, 0, mLength);
|
||||
mIds = newIds;
|
||||
mSpellCheckSpans = newSpellCheckSpans;
|
||||
}
|
||||
|
||||
mIds[mLength] = mSpanSequenceCounter++;
|
||||
mSpellCheckSpans[mLength] = spellCheckSpan;
|
||||
mSpellCheckSpans[mLength] = new SpellCheckSpan();
|
||||
mLength++;
|
||||
return mLength - 1;
|
||||
}
|
||||
|
||||
if (DEBUG_SPELL_CHECK) {
|
||||
final Editable mText = (Editable) mTextView.getText();
|
||||
int start = mText.getSpanStart(spellCheckSpan);
|
||||
int end = mText.getSpanEnd(spellCheckSpan);
|
||||
if (start >= 0 && end >= 0) {
|
||||
Log.d(LOG_TAG, "Schedule check " + mText.subSequence(start, end));
|
||||
} else {
|
||||
Log.d(LOG_TAG, "Schedule check EMPTY!");
|
||||
}
|
||||
}
|
||||
|
||||
scheduleSpellCheck();
|
||||
public void addSpellCheckSpan(int wordStart, int wordEnd) {
|
||||
final int index = nextSpellCheckSpanIndex();
|
||||
((Editable) mTextView.getText()).setSpan(mSpellCheckSpans[index], wordStart, wordEnd,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
mIds[index] = mSpanSequenceCounter++;
|
||||
}
|
||||
|
||||
public void removeSpellCheckSpan(SpellCheckSpan spellCheckSpan) {
|
||||
for (int i = 0; i < mLength; i++) {
|
||||
if (mSpellCheckSpans[i] == spellCheckSpan) {
|
||||
removeAtIndex(i);
|
||||
mSpellCheckSpans[i].setSpellCheckInProgress(false);
|
||||
mIds[i] = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeAtIndex(int i) {
|
||||
System.arraycopy(mIds, i + 1, mIds, i, mLength - i - 1);
|
||||
System.arraycopy(mSpellCheckSpans, i + 1, mSpellCheckSpans, i, mLength - i - 1);
|
||||
mLength--;
|
||||
}
|
||||
|
||||
public void onSelectionChanged() {
|
||||
scheduleSpellCheck();
|
||||
spellCheck();
|
||||
}
|
||||
|
||||
private void scheduleSpellCheck() {
|
||||
if (mLength == 0) return;
|
||||
public void spellCheck() {
|
||||
if (mSpellCheckerSession == null) return;
|
||||
|
||||
if (mChecker != null) {
|
||||
mTextView.removeCallbacks(mChecker);
|
||||
}
|
||||
if (mChecker == null) {
|
||||
mChecker = new Runnable() {
|
||||
public void run() {
|
||||
spellCheck();
|
||||
}
|
||||
};
|
||||
}
|
||||
mTextView.postDelayed(mChecker, DELAY_BEFORE_SPELL_CHECK);
|
||||
}
|
||||
|
||||
private void spellCheck() {
|
||||
final Editable editable = (Editable) mTextView.getText();
|
||||
final int selectionStart = Selection.getSelectionStart(editable);
|
||||
final int selectionEnd = Selection.getSelectionEnd(editable);
|
||||
@@ -164,8 +138,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
||||
int textInfosCount = 0;
|
||||
|
||||
for (int i = 0; i < mLength; i++) {
|
||||
SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
|
||||
|
||||
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
|
||||
if (spellCheckSpan.isSpellCheckInProgress()) continue;
|
||||
|
||||
final int start = editable.getSpanStart(spellCheckSpan);
|
||||
@@ -174,7 +147,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
||||
// Do not check this word if the user is currently editing it
|
||||
if (start >= 0 && end > start && (selectionEnd < start || selectionStart > end)) {
|
||||
final String word = editable.subSequence(start, end).toString();
|
||||
spellCheckSpan.setSpellCheckInProgress();
|
||||
spellCheckSpan.setSpellCheckInProgress(true);
|
||||
textInfos[textInfosCount++] = new TextInfo(word, mCookie, mIds[i]);
|
||||
}
|
||||
}
|
||||
@@ -196,27 +169,18 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
||||
for (int i = 0; i < results.length; i++) {
|
||||
SuggestionsInfo suggestionsInfo = results[i];
|
||||
if (suggestionsInfo.getCookie() != mCookie) continue;
|
||||
|
||||
final int sequenceNumber = suggestionsInfo.getSequence();
|
||||
// Starting from the end, to limit the number of array copy while removing
|
||||
for (int j = mLength - 1; j >= 0; j--) {
|
||||
|
||||
for (int j = 0; j < mLength; j++) {
|
||||
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
|
||||
|
||||
if (sequenceNumber == mIds[j]) {
|
||||
SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
|
||||
final int attributes = suggestionsInfo.getSuggestionsAttributes();
|
||||
boolean isInDictionary =
|
||||
((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0);
|
||||
boolean looksLikeTypo =
|
||||
((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);
|
||||
|
||||
if (DEBUG_SPELL_CHECK) {
|
||||
final int start = editable.getSpanStart(spellCheckSpan);
|
||||
final int end = editable.getSpanEnd(spellCheckSpan);
|
||||
Log.d(LOG_TAG, "Result sequence=" + suggestionsInfo.getSequence() + " " +
|
||||
editable.subSequence(start, end) +
|
||||
"\t" + (isInDictionary?"IN_DICT" : "NOT_DICT") +
|
||||
"\t" + (looksLikeTypo?"TYPO" : "NOT_TYPO"));
|
||||
}
|
||||
|
||||
if (!isInDictionary && looksLikeTypo) {
|
||||
String[] suggestions = getSuggestions(suggestionsInfo);
|
||||
if (suggestions.length > 0) {
|
||||
@@ -230,13 +194,6 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
// TODO limit to the word rectangle region
|
||||
mTextView.invalidate();
|
||||
|
||||
if (DEBUG_SPELL_CHECK) {
|
||||
String suggestionsString = "";
|
||||
for (String s : suggestions) { suggestionsString += s + "|"; }
|
||||
Log.d(LOG_TAG, " Suggestions for " + sequenceNumber + " " +
|
||||
editable.subSequence(start, end)+ " " + suggestionsString);
|
||||
}
|
||||
}
|
||||
}
|
||||
editable.removeSpan(spellCheckSpan);
|
||||
@@ -246,9 +203,10 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
||||
}
|
||||
|
||||
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) {
|
||||
for (int j = 0; j < len; j++) {
|
||||
suggestions[j] = suggestionsInfo.getSuggestionAt(j);
|
||||
}
|
||||
return suggestions;
|
||||
|
||||
@@ -5537,7 +5537,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
@Override public boolean onCheckIsTextEditor() {
|
||||
return mInputType != EditorInfo.TYPE_NULL;
|
||||
}
|
||||
|
||||
|
||||
@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
||||
if (onCheckIsTextEditor() && isEnabled()) {
|
||||
if (mInputMethodState == null) {
|
||||
@@ -7492,9 +7492,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
*/
|
||||
protected void onSelectionChanged(int selStart, int selEnd) {
|
||||
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
|
||||
if (mSpellChecker != null) {
|
||||
mSpellChecker.onSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7553,6 +7550,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
for (int i = 0; i < length; i++) {
|
||||
final int s = text.getSpanStart(spans[i]);
|
||||
final int e = text.getSpanEnd(spans[i]);
|
||||
// Spans that are adjacent to the edited region will be handled in
|
||||
// updateSpellCheckSpans. Result depends on what will be added (space or text)
|
||||
if (e == start || s == end) break;
|
||||
text.removeSpan(spans[i]);
|
||||
}
|
||||
@@ -7735,12 +7734,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
}
|
||||
|
||||
if (what instanceof SpellCheckSpan) {
|
||||
if (newStart < 0) {
|
||||
getSpellChecker().removeSpellCheckSpan((SpellCheckSpan) what);
|
||||
} else if (oldStart < 0) {
|
||||
getSpellChecker().addSpellCheckSpan((SpellCheckSpan) what);
|
||||
}
|
||||
if (newStart < 0 && what instanceof SpellCheckSpan) {
|
||||
getSpellChecker().removeSpellCheckSpan((SpellCheckSpan) what);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7750,8 +7745,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
private void updateSpellCheckSpans(int start, int end) {
|
||||
if (!isTextEditable() || !isSuggestionsEnabled() || !getSpellChecker().isSessionActive())
|
||||
return;
|
||||
Editable text = (Editable) mText;
|
||||
|
||||
Editable text = (Editable) mText;
|
||||
WordIterator wordIterator = getWordIterator();
|
||||
wordIterator.setCharSequence(text);
|
||||
|
||||
@@ -7770,57 +7765,75 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to expand by one character because we want to include the spans that end/start
|
||||
// at position start/end respectively.
|
||||
SpellCheckSpan[] spellCheckSpans = text.getSpans(start - 1, end + 1, SpellCheckSpan.class);
|
||||
SuggestionSpan[] suggestionSpans = text.getSpans(start - 1, end + 1, SuggestionSpan.class);
|
||||
final int numberOfSpellCheckSpans = spellCheckSpans.length;
|
||||
|
||||
// Iterate over the newly added text and schedule new SpellCheckSpans
|
||||
while (wordStart <= end) {
|
||||
if (wordEnd >= start) {
|
||||
// A word across the interval boundaries must remove boundary edition spans
|
||||
// A new word has been created across the interval boundaries. Remove previous spans
|
||||
if (wordStart < start && wordEnd > start) {
|
||||
removeEditionSpansAt(start, text);
|
||||
removeSpansAt(start, spellCheckSpans, text);
|
||||
removeSpansAt(start, suggestionSpans, text);
|
||||
}
|
||||
|
||||
if (wordStart < end && wordEnd > end) {
|
||||
removeEditionSpansAt(end, text);
|
||||
removeSpansAt(end, spellCheckSpans, text);
|
||||
removeSpansAt(end, suggestionSpans, text);
|
||||
}
|
||||
|
||||
// Do not create new boundary spans if they already exist
|
||||
boolean createSpellCheckSpan = true;
|
||||
if (wordEnd == start) {
|
||||
SpellCheckSpan[] spellCheckSpans = text.getSpans(start, start,
|
||||
SpellCheckSpan.class);
|
||||
if (spellCheckSpans.length > 0) createSpellCheckSpan = false;
|
||||
for (int i = 0; i < numberOfSpellCheckSpans; i++) {
|
||||
final int spanEnd = text.getSpanEnd(spellCheckSpans[i]);
|
||||
if (spanEnd == start) {
|
||||
createSpellCheckSpan = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wordStart == end) {
|
||||
SpellCheckSpan[] spellCheckSpans = text.getSpans(end, end,
|
||||
SpellCheckSpan.class);
|
||||
if (spellCheckSpans.length > 0) createSpellCheckSpan = false;
|
||||
for (int i = 0; i < numberOfSpellCheckSpans; i++) {
|
||||
final int spanStart = text.getSpanEnd(spellCheckSpans[i]);
|
||||
if (spanStart == end) {
|
||||
createSpellCheckSpan = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (createSpellCheckSpan) {
|
||||
text.setSpan(new SpellCheckSpan(), wordStart, wordEnd,
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
mSpellChecker.addSpellCheckSpan(wordStart, wordEnd);
|
||||
}
|
||||
}
|
||||
|
||||
// iterate word by word
|
||||
wordEnd = wordIterator.following(wordEnd);
|
||||
if (wordEnd == BreakIterator.DONE) return;
|
||||
if (wordEnd == BreakIterator.DONE) break;
|
||||
wordStart = wordIterator.getBeginning(wordEnd);
|
||||
if (wordStart == BreakIterator.DONE) {
|
||||
Log.e(LOG_TAG, "Unable to find word beginning from " + wordEnd + "in " + mText);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mSpellChecker.spellCheck();
|
||||
}
|
||||
|
||||
private static void removeEditionSpansAt(int offset, Editable text) {
|
||||
SuggestionSpan[] suggestionSpans = text.getSpans(offset, offset, SuggestionSpan.class);
|
||||
for (int i = 0; i < suggestionSpans.length; i++) {
|
||||
text.removeSpan(suggestionSpans[i]);
|
||||
}
|
||||
SpellCheckSpan[] spellCheckSpans = text.getSpans(offset, offset, SpellCheckSpan.class);
|
||||
for (int i = 0; i < spellCheckSpans.length; i++) {
|
||||
text.removeSpan(spellCheckSpans[i]);
|
||||
private static <T> void removeSpansAt(int offset, T[] spans, Editable text) {
|
||||
final int length = spans.length;
|
||||
for (int i = 0; i < length; i++) {
|
||||
final T span = spans[i];
|
||||
final int start = text.getSpanStart(span);
|
||||
if (start > offset) continue;
|
||||
final int end = text.getSpanEnd(span);
|
||||
if (end < offset) continue;
|
||||
text.removeSpan(span);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8381,6 +8394,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
|
||||
hideControllers();
|
||||
if (!selectAllGotFocus && mText.length() > 0) {
|
||||
if (mSpellChecker != null) {
|
||||
// When the cursor moves, the word that was typed may need spell check
|
||||
mSpellChecker.onSelectionChanged();
|
||||
}
|
||||
if (isCursorInsideEasyCorrectionSpan()) {
|
||||
showSuggestions();
|
||||
} else if (hasInsertionController()) {
|
||||
|
||||
Reference in New Issue
Block a user