Loading core/java/android/view/textservice/SentenceSuggestionsInfo.java +7 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,13 @@ public final class SentenceSuggestionsInfo implements Parcelable { return 0; } /** * @hide */ public int getSuggestionsCount() { return mSuggestionsInfos.length; } /** * @hide */ Loading core/java/android/view/textservice/SpellCheckerSession.java +2 −2 Original line number Diff line number Diff line Loading @@ -180,9 +180,9 @@ public class SpellCheckerSession { /** * @hide */ public void getSentenceSuggestions(TextInfo textInfo, int suggestionsLimit) { public void getSentenceSuggestions(TextInfo[] textInfo, int suggestionsLimit) { mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple( new TextInfo[] {textInfo}, suggestionsLimit); textInfo, suggestionsLimit); } /** Loading core/java/android/widget/SpellChecker.java +165 −84 Original line number Diff line number Diff line Loading @@ -57,9 +57,14 @@ public class SpellChecker implements SpellCheckerSessionListener { // Pause between each spell check to keep the UI smooth private final static int SPELL_PAUSE_DURATION = 400; // milliseconds private static final int USE_SPAN_RANGE = -1; private final TextView mTextView; SpellCheckerSession mSpellCheckerSession; // We assume that the sentence level spell check will always provide better results than words. // Although word SC has a sequential option. private boolean mIsSentenceSpellCheckSupported; final int mCookie; // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated Loading Loading @@ -111,6 +116,7 @@ public class SpellChecker implements SpellCheckerSessionListener { null /* Bundle not currently used by the textServicesManager */, mCurrentLocale, this, false /* means any available languages from current spell checker */); mIsSentenceSpellCheckSupported = mSpellCheckerSession.isSentenceSpellCheckSupported(); } // Restore SpellCheckSpans in pool Loading Loading @@ -272,46 +278,80 @@ public class SpellChecker implements SpellCheckerSessionListener { textInfos = textInfosCopy; } if (mIsSentenceSpellCheckSupported) { mSpellCheckerSession.getSentenceSuggestions( textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE); } else { mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE, false /* TODO Set sequentialWords to true for initial spell check */); } } @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { // TODO: Handle the position and length for each suggestion // do nothing for now } @Override public void onGetSuggestions(SuggestionsInfo[] results) { Editable editable = (Editable) mTextView.getText(); for (int i = 0; i < results.length; i++) { SuggestionsInfo suggestionsInfo = results[i]; if (suggestionsInfo.getCookie() != mCookie) continue; private SpellCheckSpan onGetSuggestionsInternal( SuggestionsInfo suggestionsInfo, int offset, int length) { if (suggestionsInfo.getCookie() != mCookie) { return null; } final Editable editable = (Editable) mTextView.getText(); final int sequenceNumber = suggestionsInfo.getSequence(); for (int j = 0; j < mLength; j++) { if (sequenceNumber == mIds[j]) { for (int k = 0; k < mLength; ++k) { if (sequenceNumber == mIds[k]) { final int attributes = suggestionsInfo.getSuggestionsAttributes(); boolean isInDictionary = final boolean isInDictionary = ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0); boolean looksLikeTypo = final boolean looksLikeTypo = ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0); SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j]; final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[k]; //TODO: we need to change that rule for results from a sentence-level spell // checker that will probably be in dictionary. if (!isInDictionary && looksLikeTypo) { createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan); createMisspelledSuggestionSpan( editable, suggestionsInfo, spellCheckSpan, offset, length); } return spellCheckSpan; } } return null; } @Override public void onGetSuggestions(SuggestionsInfo[] results) { final Editable editable = (Editable) mTextView.getText(); for (int i = 0; i < results.length; ++i) { final SpellCheckSpan spellCheckSpan = onGetSuggestionsInternal(results[i], USE_SPAN_RANGE, USE_SPAN_RANGE); if (spellCheckSpan != null) { editable.removeSpan(spellCheckSpan); break; } } scheduleNewSpellCheck(); } @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { final Editable editable = (Editable) mTextView.getText(); for (int i = 0; i < results.length; ++i) { final SentenceSuggestionsInfo ssi = results[i]; SpellCheckSpan spellCheckSpan = null; for (int j = 0; j < ssi.getSuggestionsCount(); ++j) { final SuggestionsInfo suggestionsInfo = ssi.getSuggestionsInfoAt(j); final int offset = ssi.getOffsetAt(j); final int length = ssi.getLengthAt(j); final SpellCheckSpan scs = onGetSuggestionsInternal( suggestionsInfo, offset, length); if (spellCheckSpan == null && scs != null) { // the spellCheckSpan is shared by all the "SuggestionsInfo"s in the same // SentenceSuggestionsInfo spellCheckSpan = scs; } } if (spellCheckSpan != null) { editable.removeSpan(spellCheckSpan); } } scheduleNewSpellCheck(); } Loading @@ -338,10 +378,11 @@ public class SpellChecker implements SpellCheckerSessionListener { } private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo, SpellCheckSpan spellCheckSpan) { final int start = editable.getSpanStart(spellCheckSpan); final int end = editable.getSpanEnd(spellCheckSpan); if (start < 0 || end <= start) return; // span was removed in the meantime SpellCheckSpan spellCheckSpan, int offset, int length) { final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan); final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan); if (spellCheckSpanStart < 0 || spellCheckSpanEnd <= spellCheckSpanStart) return; // span was removed in the meantime final int suggestionsCount = suggestionsInfo.getSuggestionsCount(); if (suggestionsCount <= 0) { Loading @@ -349,6 +390,16 @@ public class SpellChecker implements SpellCheckerSessionListener { return; } final int start; final int end; if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) { start = spellCheckSpanStart + offset; end = start + length; } else { start = spellCheckSpanStart; end = spellCheckSpanEnd; } String[] suggestions = new String[suggestionsCount]; for (int i = 0; i < suggestionsCount; i++) { suggestions[i] = suggestionsInfo.getSuggestionAt(i); Loading Loading @@ -419,16 +470,43 @@ public class SpellChecker implements SpellCheckerSessionListener { int wordCount = 0; boolean scheduleOtherSpellCheck = false; if (mIsSentenceSpellCheckSupported) { int regionEnd; if (wordIteratorWindowEnd < end) { // Several batches needed on that region. Cut after last previous word regionEnd = mWordIterator.preceding(wordIteratorWindowEnd); scheduleOtherSpellCheck = true; } else { regionEnd = mWordIterator.preceding(end); } boolean correct = regionEnd != BreakIterator.DONE; if (correct) { regionEnd = mWordIterator.getEnd(regionEnd); correct = regionEnd != BreakIterator.DONE; } if (!correct) { editable.removeSpan(mRange); return; } wordStart = regionEnd; // TODO: Find the start position of the sentence. // Set span with the context final int spellCheckStart = Math.min( start, Math.max(wordStart, regionEnd - WORD_ITERATOR_INTERVAL)); if (regionEnd <= spellCheckStart) { return; } addSpellCheckSpan(editable, spellCheckStart, regionEnd); } else { while (wordStart <= end) { if (wordEnd >= start && wordEnd > wordStart) { if (wordCount >= MAX_NUMBER_OF_WORDS) { scheduleOtherSpellCheck = true; break; } // A new word has been created across the interval boundaries with this edit. // The previous spans (that ended on start / started on end) are not valid // anymore and must be removed. // A new word has been created across the interval boundaries with this // edit. The previous spans (that ended on start / started on end) are // not valid anymore and must be removed. if (wordStart < start && wordEnd > start) { removeSpansAt(editable, start, spellCheckSpans); removeSpansAt(editable, start, suggestionSpans); Loading Loading @@ -472,8 +550,10 @@ public class SpellChecker implements SpellCheckerSessionListener { wordEnd = mWordIterator.following(wordEnd); if ((wordIteratorWindowEnd < end) && (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) { wordIteratorWindowEnd = Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL); mWordIterator.setCharSequence(editable, originalWordEnd, wordIteratorWindowEnd); wordIteratorWindowEnd = Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL); mWordIterator.setCharSequence( editable, originalWordEnd, wordIteratorWindowEnd); wordEnd = mWordIterator.following(originalWordEnd); } if (wordEnd == BreakIterator.DONE) break; Loading @@ -482,6 +562,7 @@ public class SpellChecker implements SpellCheckerSessionListener { break; } } } if (scheduleOtherSpellCheck) { // Update range span: start new spell check from last wordStart Loading Loading
core/java/android/view/textservice/SentenceSuggestionsInfo.java +7 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,13 @@ public final class SentenceSuggestionsInfo implements Parcelable { return 0; } /** * @hide */ public int getSuggestionsCount() { return mSuggestionsInfos.length; } /** * @hide */ Loading
core/java/android/view/textservice/SpellCheckerSession.java +2 −2 Original line number Diff line number Diff line Loading @@ -180,9 +180,9 @@ public class SpellCheckerSession { /** * @hide */ public void getSentenceSuggestions(TextInfo textInfo, int suggestionsLimit) { public void getSentenceSuggestions(TextInfo[] textInfo, int suggestionsLimit) { mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple( new TextInfo[] {textInfo}, suggestionsLimit); textInfo, suggestionsLimit); } /** Loading
core/java/android/widget/SpellChecker.java +165 −84 Original line number Diff line number Diff line Loading @@ -57,9 +57,14 @@ public class SpellChecker implements SpellCheckerSessionListener { // Pause between each spell check to keep the UI smooth private final static int SPELL_PAUSE_DURATION = 400; // milliseconds private static final int USE_SPAN_RANGE = -1; private final TextView mTextView; SpellCheckerSession mSpellCheckerSession; // We assume that the sentence level spell check will always provide better results than words. // Although word SC has a sequential option. private boolean mIsSentenceSpellCheckSupported; final int mCookie; // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated Loading Loading @@ -111,6 +116,7 @@ public class SpellChecker implements SpellCheckerSessionListener { null /* Bundle not currently used by the textServicesManager */, mCurrentLocale, this, false /* means any available languages from current spell checker */); mIsSentenceSpellCheckSupported = mSpellCheckerSession.isSentenceSpellCheckSupported(); } // Restore SpellCheckSpans in pool Loading Loading @@ -272,46 +278,80 @@ public class SpellChecker implements SpellCheckerSessionListener { textInfos = textInfosCopy; } if (mIsSentenceSpellCheckSupported) { mSpellCheckerSession.getSentenceSuggestions( textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE); } else { mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE, false /* TODO Set sequentialWords to true for initial spell check */); } } @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { // TODO: Handle the position and length for each suggestion // do nothing for now } @Override public void onGetSuggestions(SuggestionsInfo[] results) { Editable editable = (Editable) mTextView.getText(); for (int i = 0; i < results.length; i++) { SuggestionsInfo suggestionsInfo = results[i]; if (suggestionsInfo.getCookie() != mCookie) continue; private SpellCheckSpan onGetSuggestionsInternal( SuggestionsInfo suggestionsInfo, int offset, int length) { if (suggestionsInfo.getCookie() != mCookie) { return null; } final Editable editable = (Editable) mTextView.getText(); final int sequenceNumber = suggestionsInfo.getSequence(); for (int j = 0; j < mLength; j++) { if (sequenceNumber == mIds[j]) { for (int k = 0; k < mLength; ++k) { if (sequenceNumber == mIds[k]) { final int attributes = suggestionsInfo.getSuggestionsAttributes(); boolean isInDictionary = final boolean isInDictionary = ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0); boolean looksLikeTypo = final boolean looksLikeTypo = ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0); SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j]; final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[k]; //TODO: we need to change that rule for results from a sentence-level spell // checker that will probably be in dictionary. if (!isInDictionary && looksLikeTypo) { createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan); createMisspelledSuggestionSpan( editable, suggestionsInfo, spellCheckSpan, offset, length); } return spellCheckSpan; } } return null; } @Override public void onGetSuggestions(SuggestionsInfo[] results) { final Editable editable = (Editable) mTextView.getText(); for (int i = 0; i < results.length; ++i) { final SpellCheckSpan spellCheckSpan = onGetSuggestionsInternal(results[i], USE_SPAN_RANGE, USE_SPAN_RANGE); if (spellCheckSpan != null) { editable.removeSpan(spellCheckSpan); break; } } scheduleNewSpellCheck(); } @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { final Editable editable = (Editable) mTextView.getText(); for (int i = 0; i < results.length; ++i) { final SentenceSuggestionsInfo ssi = results[i]; SpellCheckSpan spellCheckSpan = null; for (int j = 0; j < ssi.getSuggestionsCount(); ++j) { final SuggestionsInfo suggestionsInfo = ssi.getSuggestionsInfoAt(j); final int offset = ssi.getOffsetAt(j); final int length = ssi.getLengthAt(j); final SpellCheckSpan scs = onGetSuggestionsInternal( suggestionsInfo, offset, length); if (spellCheckSpan == null && scs != null) { // the spellCheckSpan is shared by all the "SuggestionsInfo"s in the same // SentenceSuggestionsInfo spellCheckSpan = scs; } } if (spellCheckSpan != null) { editable.removeSpan(spellCheckSpan); } } scheduleNewSpellCheck(); } Loading @@ -338,10 +378,11 @@ public class SpellChecker implements SpellCheckerSessionListener { } private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo, SpellCheckSpan spellCheckSpan) { final int start = editable.getSpanStart(spellCheckSpan); final int end = editable.getSpanEnd(spellCheckSpan); if (start < 0 || end <= start) return; // span was removed in the meantime SpellCheckSpan spellCheckSpan, int offset, int length) { final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan); final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan); if (spellCheckSpanStart < 0 || spellCheckSpanEnd <= spellCheckSpanStart) return; // span was removed in the meantime final int suggestionsCount = suggestionsInfo.getSuggestionsCount(); if (suggestionsCount <= 0) { Loading @@ -349,6 +390,16 @@ public class SpellChecker implements SpellCheckerSessionListener { return; } final int start; final int end; if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) { start = spellCheckSpanStart + offset; end = start + length; } else { start = spellCheckSpanStart; end = spellCheckSpanEnd; } String[] suggestions = new String[suggestionsCount]; for (int i = 0; i < suggestionsCount; i++) { suggestions[i] = suggestionsInfo.getSuggestionAt(i); Loading Loading @@ -419,16 +470,43 @@ public class SpellChecker implements SpellCheckerSessionListener { int wordCount = 0; boolean scheduleOtherSpellCheck = false; if (mIsSentenceSpellCheckSupported) { int regionEnd; if (wordIteratorWindowEnd < end) { // Several batches needed on that region. Cut after last previous word regionEnd = mWordIterator.preceding(wordIteratorWindowEnd); scheduleOtherSpellCheck = true; } else { regionEnd = mWordIterator.preceding(end); } boolean correct = regionEnd != BreakIterator.DONE; if (correct) { regionEnd = mWordIterator.getEnd(regionEnd); correct = regionEnd != BreakIterator.DONE; } if (!correct) { editable.removeSpan(mRange); return; } wordStart = regionEnd; // TODO: Find the start position of the sentence. // Set span with the context final int spellCheckStart = Math.min( start, Math.max(wordStart, regionEnd - WORD_ITERATOR_INTERVAL)); if (regionEnd <= spellCheckStart) { return; } addSpellCheckSpan(editable, spellCheckStart, regionEnd); } else { while (wordStart <= end) { if (wordEnd >= start && wordEnd > wordStart) { if (wordCount >= MAX_NUMBER_OF_WORDS) { scheduleOtherSpellCheck = true; break; } // A new word has been created across the interval boundaries with this edit. // The previous spans (that ended on start / started on end) are not valid // anymore and must be removed. // A new word has been created across the interval boundaries with this // edit. The previous spans (that ended on start / started on end) are // not valid anymore and must be removed. if (wordStart < start && wordEnd > start) { removeSpansAt(editable, start, spellCheckSpans); removeSpansAt(editable, start, suggestionSpans); Loading Loading @@ -472,8 +550,10 @@ public class SpellChecker implements SpellCheckerSessionListener { wordEnd = mWordIterator.following(wordEnd); if ((wordIteratorWindowEnd < end) && (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) { wordIteratorWindowEnd = Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL); mWordIterator.setCharSequence(editable, originalWordEnd, wordIteratorWindowEnd); wordIteratorWindowEnd = Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL); mWordIterator.setCharSequence( editable, originalWordEnd, wordIteratorWindowEnd); wordEnd = mWordIterator.following(originalWordEnd); } if (wordEnd == BreakIterator.DONE) break; Loading @@ -482,6 +562,7 @@ public class SpellChecker implements SpellCheckerSessionListener { break; } } } if (scheduleOtherSpellCheck) { // Update range span: start new spell check from last wordStart Loading