SpellChecker.java revision a4c82c1c78cdd37e0dea1b5b2f44c25cc584034c
10eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne/* 20eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * Copyright (C) 2011 The Android Open Source Project 30eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * 40eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * Licensed under the Apache License, Version 2.0 (the "License"); 50eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * you may not use this file except in compliance with the License. 60eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * You may obtain a copy of the License at 70eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * 80eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * http://www.apache.org/licenses/LICENSE-2.0 90eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * 100eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * Unless required by applicable law or agreed to in writing, software 110eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * distributed under the License is distributed on an "AS IS" BASIS, 120eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * See the License for the specific language governing permissions and 140eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne * limitations under the License. 150eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne */ 166435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 176435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunnepackage android.widget; 186435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 196435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.content.Context; 206435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.text.Editable; 216435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.text.Selection; 22653d3a27878d5358b4a91518a756f6b9b3407b07Gilles Debunneimport android.text.SpannableStringBuilder; 236435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.text.Spanned; 248589474d269818713c86ee5e69a685584d1c62e7satokimport android.text.TextUtils; 25287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunneimport android.text.method.WordIterator; 266435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.text.style.SpellCheckSpan; 276435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.text.style.SuggestionSpan; 28e1e874854ab8b73dc5f2346108cbfe90dabaea18satokimport android.util.Log; 298589474d269818713c86ee5e69a685584d1c62e7satokimport android.util.LruCache; 30d404fe110558bd2e1960b428db6a2ee8bfd040cdsatokimport android.view.textservice.SentenceSuggestionsInfo; 316435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.SpellCheckerSession; 326435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; 336435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.SuggestionsInfo; 346435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.TextInfo; 356435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.TextServicesManager; 366435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 376435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport com.android.internal.util.ArrayUtils; 386435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 39287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunneimport java.text.BreakIterator; 409d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunneimport java.util.Locale; 41287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 426435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 436435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne/** 446435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne * Helper class for TextView. Bridge between the TextView and the Dictionnary service. 456435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne * 466435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne * @hide 476435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne */ 486435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunnepublic class SpellChecker implements SpellCheckerSessionListener { 49e1e874854ab8b73dc5f2346108cbfe90dabaea18satok private static final String TAG = SpellChecker.class.getSimpleName(); 50e1e874854ab8b73dc5f2346108cbfe90dabaea18satok private static final boolean DBG = false; 516435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 5235199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne // No more than this number of words will be parsed on each iteration to ensure a minimum 5335199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne // lock of the UI thread 54be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne public static final int MAX_NUMBER_OF_WORDS = 50; 5535199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne 56be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne // Rough estimate, such that the word iterator interval usually does not need to be shifted 57be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne public static final int AVERAGE_WORD_LENGTH = 7; 5835199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne 5935199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne // When parsing, use a character window of that size. Will be shifted if needed 6035199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne public static final int WORD_ITERATOR_INTERVAL = AVERAGE_WORD_LENGTH * MAX_NUMBER_OF_WORDS; 6135199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne 62be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne // Pause between each spell check to keep the UI smooth 6335199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne private final static int SPELL_PAUSE_DURATION = 400; // milliseconds 64287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 65979de903695275bae08f2e7a4e99d342f0facd23satok private static final int MIN_SENTENCE_LENGTH = 50; 66979de903695275bae08f2e7a4e99d342f0facd23satok 678898358bfdf4693af02ad454e1deb8034379ce02satok private static final int USE_SPAN_RANGE = -1; 688898358bfdf4693af02ad454e1deb8034379ce02satok 696435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private final TextView mTextView; 706435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 719d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne SpellCheckerSession mSpellCheckerSession; 728898358bfdf4693af02ad454e1deb8034379ce02satok // We assume that the sentence level spell check will always provide better results than words. 738898358bfdf4693af02ad454e1deb8034379ce02satok // Although word SC has a sequential option. 748898358bfdf4693af02ad454e1deb8034379ce02satok private boolean mIsSentenceSpellCheckSupported; 756435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final int mCookie; 766435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 77b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated 78b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne // SpellCheckSpan has been recycled and can be-reused. 79287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne // Contains null SpellCheckSpans after index mLength. 806435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private int[] mIds; 816435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private SpellCheckSpan[] mSpellCheckSpans; 82b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne // The mLength first elements of the above arrays have been initialized 836435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private int mLength; 846435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 85287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne // Parsers on chunck of text, cutting text into words that will be checked 86287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne private SpellParser[] mSpellParsers = new SpellParser[0]; 87287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 886435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private int mSpanSequenceCounter = 0; 896435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 909d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne private Locale mCurrentLocale; 919d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 929d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne // Shared by all SpellParsers. Cannot be shared with TextView since it may be used 939d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne // concurrently due to the asynchronous nature of onGetSuggestions. 949d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne private WordIterator mWordIterator; 959d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 96a49ba2f391cd0753eb12d2707bf9fe128e6566f0Gilles Debunne private TextServicesManager mTextServicesManager; 97a49ba2f391cd0753eb12d2707bf9fe128e6566f0Gilles Debunne 98be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne private Runnable mSpellRunnable; 99be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne 1008589474d269818713c86ee5e69a685584d1c62e7satok private static final int SUGGESTION_SPAN_CACHE_SIZE = 10; 1018589474d269818713c86ee5e69a685584d1c62e7satok private final LruCache<Long, SuggestionSpan> mSuggestionSpanCache = 1028589474d269818713c86ee5e69a685584d1c62e7satok new LruCache<Long, SuggestionSpan>(SUGGESTION_SPAN_CACHE_SIZE); 1038589474d269818713c86ee5e69a685584d1c62e7satok 1046435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne public SpellChecker(TextView textView) { 1056435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mTextView = textView; 1066435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1079d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne // Arbitrary: these arrays will automatically double their sizes on demand 108b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne final int size = ArrayUtils.idealObjectArraySize(1); 1096435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mIds = new int[size]; 1106435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mSpellCheckSpans = new SpellCheckSpan[size]; 1119d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 11205f24700613fb4dce95fb6d5f8fe460d7a30c128satok setLocale(mTextView.getTextServicesLocale()); 1139d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 1149d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne mCookie = hashCode(); 1159d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne } 1169d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 117249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne private void resetSession() { 118567354474f7893313d63b2bd13d07bf92aa729d3Marco Nelissen closeSession(); 119249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne 120249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne mTextServicesManager = (TextServicesManager) mTextView.getContext(). 121249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); 122f43305fb057e0818db456065fba9698e2163a762satok if (!mTextServicesManager.isSpellCheckerEnabled() 123f43305fb057e0818db456065fba9698e2163a762satok || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) { 1249b3855b75246596fc704825dd92f9269f52cbe64satok mSpellCheckerSession = null; 1259b3855b75246596fc704825dd92f9269f52cbe64satok } else { 126249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession( 1279b3855b75246596fc704825dd92f9269f52cbe64satok null /* Bundle not currently used by the textServicesManager */, 128249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne mCurrentLocale, this, 1299b3855b75246596fc704825dd92f9269f52cbe64satok false /* means any available languages from current spell checker */); 130c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok mIsSentenceSpellCheckSupported = true; 1319b3855b75246596fc704825dd92f9269f52cbe64satok } 1329d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 1339d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne // Restore SpellCheckSpans in pool 1349d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne for (int i = 0; i < mLength; i++) { 1358589474d269818713c86ee5e69a685584d1c62e7satok // Resets id and progress to invalidate spell check span 1369d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne mSpellCheckSpans[i].setSpellCheckInProgress(false); 1379d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne mIds[i] = -1; 1389d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne } 1396435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mLength = 0; 1409d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 141249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne // Remove existing misspelled SuggestionSpans 142249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne mTextView.removeMisspelledSpans((Editable) mTextView.getText()); 1438589474d269818713c86ee5e69a685584d1c62e7satok mSuggestionSpanCache.evictAll(); 144249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne } 145249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne 146249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne private void setLocale(Locale locale) { 147249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne mCurrentLocale = locale; 148249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne 149249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne resetSession(); 150249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne 151249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne // Change SpellParsers' wordIterator locale 152249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne mWordIterator = new WordIterator(locale); 1539d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 154249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne // This class is the listener for locale change: warn other locale-aware objects 1559d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne mTextView.onLocaleChanged(); 1566435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1576435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 158186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne /** 159186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne * @return true if a spell checker session has successfully been created. Returns false if not, 160186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne * for instance when spell checking has been disabled in settings. 161186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne */ 162287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne private boolean isSessionActive() { 163186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne return mSpellCheckerSession != null; 164186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne } 165186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne 166186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne public void closeSession() { 167186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne if (mSpellCheckerSession != null) { 168186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne mSpellCheckerSession.close(); 169186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne } 170287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 171287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne final int length = mSpellParsers.length; 172287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne for (int i = 0; i < length; i++) { 173e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne mSpellParsers[i].stop(); 174287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 175be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne 176be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne if (mSpellRunnable != null) { 177be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne mTextView.removeCallbacks(mSpellRunnable); 178be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne } 179186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne } 180186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne 181b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne private int nextSpellCheckSpanIndex() { 182b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne for (int i = 0; i < mLength; i++) { 183b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne if (mIds[i] < 0) return i; 184b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne } 185b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne 186b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne if (mLength == mSpellCheckSpans.length) { 187b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne final int newSize = mLength * 2; 1886435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne int[] newIds = new int[newSize]; 1896435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne SpellCheckSpan[] newSpellCheckSpans = new SpellCheckSpan[newSize]; 190b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne System.arraycopy(mIds, 0, newIds, 0, mLength); 191b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne System.arraycopy(mSpellCheckSpans, 0, newSpellCheckSpans, 0, mLength); 1926435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mIds = newIds; 1936435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mSpellCheckSpans = newSpellCheckSpans; 1946435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1956435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 196b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne mSpellCheckSpans[mLength] = new SpellCheckSpan(); 1976435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mLength++; 198b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne return mLength - 1; 199b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne } 2006435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 201f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne private void addSpellCheckSpan(Editable editable, int start, int end) { 202b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne final int index = nextSpellCheckSpanIndex(); 203f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne editable.setSpan(mSpellCheckSpans[index], start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 204b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne mIds[index] = mSpanSequenceCounter++; 2056435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 2066435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 2076435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne public void removeSpellCheckSpan(SpellCheckSpan spellCheckSpan) { 2086435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne for (int i = 0; i < mLength; i++) { 2096435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (mSpellCheckSpans[i] == spellCheckSpan) { 2108589474d269818713c86ee5e69a685584d1c62e7satok // Resets id and progress to invalidate spell check span 211b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne mSpellCheckSpans[i].setSpellCheckInProgress(false); 212b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne mIds[i] = -1; 2136435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne return; 2146435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 2156435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 2166435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 2176435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 2186435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne public void onSelectionChanged() { 219b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne spellCheck(); 2206435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 2216435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 222287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne public void spellCheck(int start, int end) { 2238589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 2248589474d269818713c86ee5e69a685584d1c62e7satok Log.d(TAG, "Start spell-checking: " + start + ", " + end); 2258589474d269818713c86ee5e69a685584d1c62e7satok } 22605f24700613fb4dce95fb6d5f8fe460d7a30c128satok final Locale locale = mTextView.getTextServicesLocale(); 227c62589cbecef6e748bcc6c6f4ea6a8ff7656923fGilles Debunne final boolean isSessionActive = isSessionActive(); 2289d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) { 2299d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne setLocale(locale); 2309d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne // Re-check the entire text 2319d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne start = 0; 2329d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne end = mTextView.getText().length(); 233249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne } else { 234249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne final boolean spellCheckerActivated = mTextServicesManager.isSpellCheckerEnabled(); 235c62589cbecef6e748bcc6c6f4ea6a8ff7656923fGilles Debunne if (isSessionActive != spellCheckerActivated) { 236249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne // Spell checker has been turned of or off since last spellCheck 237249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne resetSession(); 238249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne } 2399d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne } 2409d8d3f1539ce5bdf512bd47ec1648609d6cde5b1Gilles Debunne 241c62589cbecef6e748bcc6c6f4ea6a8ff7656923fGilles Debunne if (!isSessionActive) return; 242287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 243e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne // Find first available SpellParser from pool 244287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne final int length = mSpellParsers.length; 245287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne for (int i = 0; i < length; i++) { 246287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne final SpellParser spellParser = mSpellParsers[i]; 247249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne if (spellParser.isFinished()) { 2480249b43f6ce59bfec104f0fe606d9059244f8797Gilles Debunne spellParser.parse(start, end); 249287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne return; 250287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 251287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 252287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 2538589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 2548589474d269818713c86ee5e69a685584d1c62e7satok Log.d(TAG, "new spell parser."); 2558589474d269818713c86ee5e69a685584d1c62e7satok } 256287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne // No available parser found in pool, create a new one 257287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne SpellParser[] newSpellParsers = new SpellParser[length + 1]; 258287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne System.arraycopy(mSpellParsers, 0, newSpellParsers, 0, length); 259287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne mSpellParsers = newSpellParsers; 260287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 261287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne SpellParser spellParser = new SpellParser(); 262287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne mSpellParsers[length] = spellParser; 2630249b43f6ce59bfec104f0fe606d9059244f8797Gilles Debunne spellParser.parse(start, end); 264287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 265287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 266287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne private void spellCheck() { 2670eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne if (mSpellCheckerSession == null) return; 2689906847cef4307896a64c68fa27da6603a7d8da2Gilles Debunne 269f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne Editable editable = (Editable) mTextView.getText(); 270f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne final int selectionStart = Selection.getSelectionStart(editable); 271f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne final int selectionEnd = Selection.getSelectionEnd(editable); 2726435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 2736435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne TextInfo[] textInfos = new TextInfo[mLength]; 2746435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne int textInfosCount = 0; 2756435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 2766435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne for (int i = 0; i < mLength; i++) { 277b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i]; 2788589474d269818713c86ee5e69a685584d1c62e7satok if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) continue; 2796435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 280f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne final int start = editable.getSpanStart(spellCheckSpan); 281f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne final int end = editable.getSpanEnd(spellCheckSpan); 2826435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 2836435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne // Do not check this word if the user is currently editing it 2848589474d269818713c86ee5e69a685584d1c62e7satok final boolean isEditing; 2858589474d269818713c86ee5e69a685584d1c62e7satok if (mIsSentenceSpellCheckSupported) { 2868589474d269818713c86ee5e69a685584d1c62e7satok // Allow the overlap of the cursor and the first boundary of the spell check span 2878589474d269818713c86ee5e69a685584d1c62e7satok // no to skip the spell check of the following word because the 2888589474d269818713c86ee5e69a685584d1c62e7satok // following word will never be spell-checked even if the user finishes composing 2898589474d269818713c86ee5e69a685584d1c62e7satok isEditing = selectionEnd <= start || selectionStart > end; 2908589474d269818713c86ee5e69a685584d1c62e7satok } else { 2918589474d269818713c86ee5e69a685584d1c62e7satok isEditing = selectionEnd < start || selectionStart > end; 2928589474d269818713c86ee5e69a685584d1c62e7satok } 2938589474d269818713c86ee5e69a685584d1c62e7satok if (start >= 0 && end > start && isEditing) { 294653d3a27878d5358b4a91518a756f6b9b3407b07Gilles Debunne final String word = (editable instanceof SpannableStringBuilder) ? 295653d3a27878d5358b4a91518a756f6b9b3407b07Gilles Debunne ((SpannableStringBuilder) editable).substring(start, end) : 296653d3a27878d5358b4a91518a756f6b9b3407b07Gilles Debunne editable.subSequence(start, end).toString(); 297b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne spellCheckSpan.setSpellCheckInProgress(true); 2986435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne textInfos[textInfosCount++] = new TextInfo(word, mCookie, mIds[i]); 299e1e874854ab8b73dc5f2346108cbfe90dabaea18satok if (DBG) { 300e1e874854ab8b73dc5f2346108cbfe90dabaea18satok Log.d(TAG, "create TextInfo: (" + i + "/" + mLength + ")" + word 301e1e874854ab8b73dc5f2346108cbfe90dabaea18satok + ", cookie = " + mCookie + ", seq = " 302e1e874854ab8b73dc5f2346108cbfe90dabaea18satok + mIds[i] + ", sel start = " + selectionStart + ", sel end = " 303e1e874854ab8b73dc5f2346108cbfe90dabaea18satok + selectionEnd + ", start = " + start + ", end = " + end); 304e1e874854ab8b73dc5f2346108cbfe90dabaea18satok } 3056435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 3066435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 3076435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 3086435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (textInfosCount > 0) { 309287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne if (textInfosCount < textInfos.length) { 3106435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne TextInfo[] textInfosCopy = new TextInfo[textInfosCount]; 3116435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne System.arraycopy(textInfos, 0, textInfosCopy, 0, textInfosCount); 3126435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne textInfos = textInfosCopy; 3136435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 31435199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne 3158898358bfdf4693af02ad454e1deb8034379ce02satok if (mIsSentenceSpellCheckSupported) { 3168898358bfdf4693af02ad454e1deb8034379ce02satok mSpellCheckerSession.getSentenceSuggestions( 3178898358bfdf4693af02ad454e1deb8034379ce02satok textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE); 3188898358bfdf4693af02ad454e1deb8034379ce02satok } else { 3198898358bfdf4693af02ad454e1deb8034379ce02satok mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE, 3208898358bfdf4693af02ad454e1deb8034379ce02satok false /* TODO Set sequentialWords to true for initial spell check */); 3218898358bfdf4693af02ad454e1deb8034379ce02satok } 3226435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 3236435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 3246435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 3258898358bfdf4693af02ad454e1deb8034379ce02satok private SpellCheckSpan onGetSuggestionsInternal( 3268898358bfdf4693af02ad454e1deb8034379ce02satok SuggestionsInfo suggestionsInfo, int offset, int length) { 327792ee0cc4d9415e45a16803c6fe3e60c53760e25satok if (suggestionsInfo == null || suggestionsInfo.getCookie() != mCookie) { 3288898358bfdf4693af02ad454e1deb8034379ce02satok return null; 3298898358bfdf4693af02ad454e1deb8034379ce02satok } 3308898358bfdf4693af02ad454e1deb8034379ce02satok final Editable editable = (Editable) mTextView.getText(); 3318898358bfdf4693af02ad454e1deb8034379ce02satok final int sequenceNumber = suggestionsInfo.getSequence(); 3328898358bfdf4693af02ad454e1deb8034379ce02satok for (int k = 0; k < mLength; ++k) { 3338898358bfdf4693af02ad454e1deb8034379ce02satok if (sequenceNumber == mIds[k]) { 3348898358bfdf4693af02ad454e1deb8034379ce02satok final int attributes = suggestionsInfo.getSuggestionsAttributes(); 3358898358bfdf4693af02ad454e1deb8034379ce02satok final boolean isInDictionary = 3368898358bfdf4693af02ad454e1deb8034379ce02satok ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0); 3378898358bfdf4693af02ad454e1deb8034379ce02satok final boolean looksLikeTypo = 3388898358bfdf4693af02ad454e1deb8034379ce02satok ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0); 3398898358bfdf4693af02ad454e1deb8034379ce02satok 3408898358bfdf4693af02ad454e1deb8034379ce02satok final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[k]; 3418898358bfdf4693af02ad454e1deb8034379ce02satok //TODO: we need to change that rule for results from a sentence-level spell 3428898358bfdf4693af02ad454e1deb8034379ce02satok // checker that will probably be in dictionary. 3438898358bfdf4693af02ad454e1deb8034379ce02satok if (!isInDictionary && looksLikeTypo) { 3448898358bfdf4693af02ad454e1deb8034379ce02satok createMisspelledSuggestionSpan( 3458898358bfdf4693af02ad454e1deb8034379ce02satok editable, suggestionsInfo, spellCheckSpan, offset, length); 3468898358bfdf4693af02ad454e1deb8034379ce02satok } 3478898358bfdf4693af02ad454e1deb8034379ce02satok return spellCheckSpan; 3488898358bfdf4693af02ad454e1deb8034379ce02satok } 3498898358bfdf4693af02ad454e1deb8034379ce02satok } 3508898358bfdf4693af02ad454e1deb8034379ce02satok return null; 3510dc1f648a09b46c45190ba1ce7daecf7fada4347satok } 3520dc1f648a09b46c45190ba1ce7daecf7fada4347satok 3530dc1f648a09b46c45190ba1ce7daecf7fada4347satok @Override 3546435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne public void onGetSuggestions(SuggestionsInfo[] results) { 3558898358bfdf4693af02ad454e1deb8034379ce02satok final Editable editable = (Editable) mTextView.getText(); 3568898358bfdf4693af02ad454e1deb8034379ce02satok for (int i = 0; i < results.length; ++i) { 3578898358bfdf4693af02ad454e1deb8034379ce02satok final SpellCheckSpan spellCheckSpan = 3588898358bfdf4693af02ad454e1deb8034379ce02satok onGetSuggestionsInternal(results[i], USE_SPAN_RANGE, USE_SPAN_RANGE); 3598898358bfdf4693af02ad454e1deb8034379ce02satok if (spellCheckSpan != null) { 3608898358bfdf4693af02ad454e1deb8034379ce02satok editable.removeSpan(spellCheckSpan); 3618898358bfdf4693af02ad454e1deb8034379ce02satok } 3628898358bfdf4693af02ad454e1deb8034379ce02satok } 3638898358bfdf4693af02ad454e1deb8034379ce02satok scheduleNewSpellCheck(); 3648898358bfdf4693af02ad454e1deb8034379ce02satok } 3658615ac9e049cdf4ab77b0897aca9bceec142c9faGilles Debunne 3668898358bfdf4693af02ad454e1deb8034379ce02satok @Override 3678898358bfdf4693af02ad454e1deb8034379ce02satok public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { 3688898358bfdf4693af02ad454e1deb8034379ce02satok final Editable editable = (Editable) mTextView.getText(); 3698898358bfdf4693af02ad454e1deb8034379ce02satok 3708898358bfdf4693af02ad454e1deb8034379ce02satok for (int i = 0; i < results.length; ++i) { 3718898358bfdf4693af02ad454e1deb8034379ce02satok final SentenceSuggestionsInfo ssi = results[i]; 372792ee0cc4d9415e45a16803c6fe3e60c53760e25satok if (ssi == null) { 373792ee0cc4d9415e45a16803c6fe3e60c53760e25satok continue; 374792ee0cc4d9415e45a16803c6fe3e60c53760e25satok } 3758898358bfdf4693af02ad454e1deb8034379ce02satok SpellCheckSpan spellCheckSpan = null; 3768898358bfdf4693af02ad454e1deb8034379ce02satok for (int j = 0; j < ssi.getSuggestionsCount(); ++j) { 3778898358bfdf4693af02ad454e1deb8034379ce02satok final SuggestionsInfo suggestionsInfo = ssi.getSuggestionsInfoAt(j); 378792ee0cc4d9415e45a16803c6fe3e60c53760e25satok if (suggestionsInfo == null) { 379792ee0cc4d9415e45a16803c6fe3e60c53760e25satok continue; 380792ee0cc4d9415e45a16803c6fe3e60c53760e25satok } 3818898358bfdf4693af02ad454e1deb8034379ce02satok final int offset = ssi.getOffsetAt(j); 3828898358bfdf4693af02ad454e1deb8034379ce02satok final int length = ssi.getLengthAt(j); 3838898358bfdf4693af02ad454e1deb8034379ce02satok final SpellCheckSpan scs = onGetSuggestionsInternal( 3848898358bfdf4693af02ad454e1deb8034379ce02satok suggestionsInfo, offset, length); 3858898358bfdf4693af02ad454e1deb8034379ce02satok if (spellCheckSpan == null && scs != null) { 3868898358bfdf4693af02ad454e1deb8034379ce02satok // the spellCheckSpan is shared by all the "SuggestionsInfo"s in the same 3878898358bfdf4693af02ad454e1deb8034379ce02satok // SentenceSuggestionsInfo 3888898358bfdf4693af02ad454e1deb8034379ce02satok spellCheckSpan = scs; 3896435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 3906435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 3918898358bfdf4693af02ad454e1deb8034379ce02satok if (spellCheckSpan != null) { 3928898358bfdf4693af02ad454e1deb8034379ce02satok editable.removeSpan(spellCheckSpan); 3938898358bfdf4693af02ad454e1deb8034379ce02satok } 3946435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 395be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne scheduleNewSpellCheck(); 396be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne } 397be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne 398be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne private void scheduleNewSpellCheck() { 3998589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 4008589474d269818713c86ee5e69a685584d1c62e7satok Log.i(TAG, "schedule new spell check."); 4018589474d269818713c86ee5e69a685584d1c62e7satok } 402be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne if (mSpellRunnable == null) { 403be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne mSpellRunnable = new Runnable() { 404be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne @Override 405be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne public void run() { 406be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne final int length = mSpellParsers.length; 407be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne for (int i = 0; i < length; i++) { 408be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne final SpellParser spellParser = mSpellParsers[i]; 409249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne if (!spellParser.isFinished()) { 410be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne spellParser.parse(); 411be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne break; // run one spell parser at a time to bound running time 412be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne } 41335199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne } 41435199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne } 415be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne }; 416be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne } else { 417be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne mTextView.removeCallbacks(mSpellRunnable); 418287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 419be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne 420be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne mTextView.postDelayed(mSpellRunnable, SPELL_PAUSE_DURATION); 4216435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 4226435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 4238615ac9e049cdf4ab77b0897aca9bceec142c9faGilles Debunne private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo, 4248898358bfdf4693af02ad454e1deb8034379ce02satok SpellCheckSpan spellCheckSpan, int offset, int length) { 4258898358bfdf4693af02ad454e1deb8034379ce02satok final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan); 4268898358bfdf4693af02ad454e1deb8034379ce02satok final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan); 4278898358bfdf4693af02ad454e1deb8034379ce02satok if (spellCheckSpanStart < 0 || spellCheckSpanEnd <= spellCheckSpanStart) 4288898358bfdf4693af02ad454e1deb8034379ce02satok return; // span was removed in the meantime 429176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 430176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne final int suggestionsCount = suggestionsInfo.getSuggestionsCount(); 431176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne if (suggestionsCount <= 0) { 432176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne // A negative suggestion count is possible 4336e0b22bea91398403ea500360b02ff2fc7a03d00Gilles Debunne return; 4346e0b22bea91398403ea500360b02ff2fc7a03d00Gilles Debunne } 435176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 4368898358bfdf4693af02ad454e1deb8034379ce02satok final int start; 4378898358bfdf4693af02ad454e1deb8034379ce02satok final int end; 4388898358bfdf4693af02ad454e1deb8034379ce02satok if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) { 4398898358bfdf4693af02ad454e1deb8034379ce02satok start = spellCheckSpanStart + offset; 4408898358bfdf4693af02ad454e1deb8034379ce02satok end = start + length; 4418898358bfdf4693af02ad454e1deb8034379ce02satok } else { 4428898358bfdf4693af02ad454e1deb8034379ce02satok start = spellCheckSpanStart; 4438898358bfdf4693af02ad454e1deb8034379ce02satok end = spellCheckSpanEnd; 4448898358bfdf4693af02ad454e1deb8034379ce02satok } 4458898358bfdf4693af02ad454e1deb8034379ce02satok 4466e0b22bea91398403ea500360b02ff2fc7a03d00Gilles Debunne String[] suggestions = new String[suggestionsCount]; 4476e0b22bea91398403ea500360b02ff2fc7a03d00Gilles Debunne for (int i = 0; i < suggestionsCount; i++) { 4486e0b22bea91398403ea500360b02ff2fc7a03d00Gilles Debunne suggestions[i] = suggestionsInfo.getSuggestionAt(i); 4496435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 450176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 451176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions, 452176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED); 4538589474d269818713c86ee5e69a685584d1c62e7satok // TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface 4548589474d269818713c86ee5e69a685584d1c62e7satok // to share the logic of word level spell checker and sentence level spell checker 4558589474d269818713c86ee5e69a685584d1c62e7satok if (mIsSentenceSpellCheckSupported) { 4568589474d269818713c86ee5e69a685584d1c62e7satok final long key = TextUtils.packRangeInLong(start, end); 4578589474d269818713c86ee5e69a685584d1c62e7satok final SuggestionSpan tempSuggestionSpan = mSuggestionSpanCache.get(key); 4588589474d269818713c86ee5e69a685584d1c62e7satok if (tempSuggestionSpan != null) { 4598589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 4608589474d269818713c86ee5e69a685584d1c62e7satok Log.i(TAG, "Cached span on the same position is cleard. " 4618589474d269818713c86ee5e69a685584d1c62e7satok + editable.subSequence(start, end)); 4628589474d269818713c86ee5e69a685584d1c62e7satok } 4638589474d269818713c86ee5e69a685584d1c62e7satok editable.removeSpan(tempSuggestionSpan); 4648589474d269818713c86ee5e69a685584d1c62e7satok } 4658589474d269818713c86ee5e69a685584d1c62e7satok mSuggestionSpanCache.put(key, suggestionSpan); 4668589474d269818713c86ee5e69a685584d1c62e7satok } 467f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 468176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 469961ebb9ab0a6d45b06a74aa90894f7fda3d528c6Gilles Debunne mTextView.invalidateRegion(start, end, false /* No cursor involved */); 4706435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 471287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 472287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne private class SpellParser { 473287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne private Object mRange = new Object(); 474287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 4750249b43f6ce59bfec104f0fe606d9059244f8797Gilles Debunne public void parse(int start, int end) { 4760249b43f6ce59bfec104f0fe606d9059244f8797Gilles Debunne if (end > start) { 4770249b43f6ce59bfec104f0fe606d9059244f8797Gilles Debunne setRangeSpan((Editable) mTextView.getText(), start, end); 4780249b43f6ce59bfec104f0fe606d9059244f8797Gilles Debunne parse(); 4790249b43f6ce59bfec104f0fe606d9059244f8797Gilles Debunne } 480e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne } 481e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne 482249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne public boolean isFinished() { 483249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne return ((Editable) mTextView.getText()).getSpanStart(mRange) < 0; 484e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne } 485e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne 486249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne public void stop() { 487249d1e827a7ffe2a034130d05e18c7b521b0de9bGilles Debunne removeRangeSpan((Editable) mTextView.getText()); 488287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 489287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 490e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne private void setRangeSpan(Editable editable, int start, int end) { 4918589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 4928589474d269818713c86ee5e69a685584d1c62e7satok Log.d(TAG, "set next range span: " + start + ", " + end); 4938589474d269818713c86ee5e69a685584d1c62e7satok } 494e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne editable.setSpan(mRange, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 495287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 496287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 497e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne private void removeRangeSpan(Editable editable) { 4988589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 4998589474d269818713c86ee5e69a685584d1c62e7satok Log.d(TAG, "Remove range span." + editable.getSpanStart(editable) 5008589474d269818713c86ee5e69a685584d1c62e7satok + editable.getSpanEnd(editable)); 5018589474d269818713c86ee5e69a685584d1c62e7satok } 502e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne editable.removeSpan(mRange); 503287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 504287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 505287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne public void parse() { 506f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne Editable editable = (Editable) mTextView.getText(); 507287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne // Iterate over the newly added text and schedule new SpellCheckSpans 50824d146b966c87fd9c3b48027cbfb4238cb892ca5satok final int start; 50924d146b966c87fd9c3b48027cbfb4238cb892ca5satok if (mIsSentenceSpellCheckSupported) { 51024d146b966c87fd9c3b48027cbfb4238cb892ca5satok // TODO: Find the start position of the sentence. 51124d146b966c87fd9c3b48027cbfb4238cb892ca5satok // Set span with the context 51224d146b966c87fd9c3b48027cbfb4238cb892ca5satok start = Math.max( 51324d146b966c87fd9c3b48027cbfb4238cb892ca5satok 0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH); 51424d146b966c87fd9c3b48027cbfb4238cb892ca5satok } else { 51524d146b966c87fd9c3b48027cbfb4238cb892ca5satok start = editable.getSpanStart(mRange); 51624d146b966c87fd9c3b48027cbfb4238cb892ca5satok } 51724d146b966c87fd9c3b48027cbfb4238cb892ca5satok 518f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne final int end = editable.getSpanEnd(mRange); 51935199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne 52035199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne int wordIteratorWindowEnd = Math.min(end, start + WORD_ITERATOR_INTERVAL); 521be5f49fb6e17e0b9588d3b94022b7e3eb6d47317Gilles Debunne mWordIterator.setCharSequence(editable, start, wordIteratorWindowEnd); 522287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 523287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne // Move back to the beginning of the current word, if any 524287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne int wordStart = mWordIterator.preceding(start); 525287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne int wordEnd; 526287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne if (wordStart == BreakIterator.DONE) { 527287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne wordEnd = mWordIterator.following(start); 528287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne if (wordEnd != BreakIterator.DONE) { 529287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne wordStart = mWordIterator.getBeginning(wordEnd); 530287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 531287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } else { 532287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne wordEnd = mWordIterator.getEnd(wordStart); 533287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 534287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne if (wordEnd == BreakIterator.DONE) { 5358589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 5368589474d269818713c86ee5e69a685584d1c62e7satok Log.i(TAG, "No more spell check."); 5378589474d269818713c86ee5e69a685584d1c62e7satok } 538e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne removeRangeSpan(editable); 539287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne return; 540287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 541287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 542287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne // We need to expand by one character because we want to include the spans that 543287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne // end/start at position start/end respectively. 544f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1, 545f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne SpellCheckSpan.class); 546f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1, 547f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne SuggestionSpan.class); 548287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 54935199f5ce7cfc433628a1beda84d80fcaa475e41Gilles Debunne int wordCount = 0; 550287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne boolean scheduleOtherSpellCheck = false; 551287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 5528898358bfdf4693af02ad454e1deb8034379ce02satok if (mIsSentenceSpellCheckSupported) { 5538898358bfdf4693af02ad454e1deb8034379ce02satok if (wordIteratorWindowEnd < end) { 5548589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 5558589474d269818713c86ee5e69a685584d1c62e7satok Log.i(TAG, "schedule other spell check."); 5568589474d269818713c86ee5e69a685584d1c62e7satok } 5578898358bfdf4693af02ad454e1deb8034379ce02satok // Several batches needed on that region. Cut after last previous word 5588898358bfdf4693af02ad454e1deb8034379ce02satok scheduleOtherSpellCheck = true; 5598898358bfdf4693af02ad454e1deb8034379ce02satok } 5608589474d269818713c86ee5e69a685584d1c62e7satok int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd); 5618589474d269818713c86ee5e69a685584d1c62e7satok boolean correct = spellCheckEnd != BreakIterator.DONE; 5628898358bfdf4693af02ad454e1deb8034379ce02satok if (correct) { 5638589474d269818713c86ee5e69a685584d1c62e7satok spellCheckEnd = mWordIterator.getEnd(spellCheckEnd); 5648589474d269818713c86ee5e69a685584d1c62e7satok correct = spellCheckEnd != BreakIterator.DONE; 5658898358bfdf4693af02ad454e1deb8034379ce02satok } 5668898358bfdf4693af02ad454e1deb8034379ce02satok if (!correct) { 5678589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 5688589474d269818713c86ee5e69a685584d1c62e7satok Log.i(TAG, "Incorrect range span."); 5698589474d269818713c86ee5e69a685584d1c62e7satok } 5708589474d269818713c86ee5e69a685584d1c62e7satok removeRangeSpan(editable); 5718898358bfdf4693af02ad454e1deb8034379ce02satok return; 5728898358bfdf4693af02ad454e1deb8034379ce02satok } 5738589474d269818713c86ee5e69a685584d1c62e7satok do { 5748589474d269818713c86ee5e69a685584d1c62e7satok // TODO: Find the start position of the sentence. 5758589474d269818713c86ee5e69a685584d1c62e7satok int spellCheckStart = wordStart; 5768589474d269818713c86ee5e69a685584d1c62e7satok boolean createSpellCheckSpan = true; 5778589474d269818713c86ee5e69a685584d1c62e7satok // Cancel or merge overlapped spell check spans 5788589474d269818713c86ee5e69a685584d1c62e7satok for (int i = 0; i < mLength; ++i) { 5798589474d269818713c86ee5e69a685584d1c62e7satok final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i]; 5808589474d269818713c86ee5e69a685584d1c62e7satok if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) { 5818589474d269818713c86ee5e69a685584d1c62e7satok continue; 5828589474d269818713c86ee5e69a685584d1c62e7satok } 5838589474d269818713c86ee5e69a685584d1c62e7satok final int spanStart = editable.getSpanStart(spellCheckSpan); 5848589474d269818713c86ee5e69a685584d1c62e7satok final int spanEnd = editable.getSpanEnd(spellCheckSpan); 5858589474d269818713c86ee5e69a685584d1c62e7satok if (spanEnd < spellCheckStart || spellCheckEnd < spanStart) { 5868589474d269818713c86ee5e69a685584d1c62e7satok // No need to merge 5878589474d269818713c86ee5e69a685584d1c62e7satok continue; 5888589474d269818713c86ee5e69a685584d1c62e7satok } 5898589474d269818713c86ee5e69a685584d1c62e7satok if (spanStart <= spellCheckStart && spellCheckEnd <= spanEnd) { 5908589474d269818713c86ee5e69a685584d1c62e7satok // There is a completely overlapped spell check span 5918589474d269818713c86ee5e69a685584d1c62e7satok // skip this span 5928589474d269818713c86ee5e69a685584d1c62e7satok createSpellCheckSpan = false; 5938589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 5948589474d269818713c86ee5e69a685584d1c62e7satok Log.i(TAG, "The range is overrapped. Skip spell check."); 5958589474d269818713c86ee5e69a685584d1c62e7satok } 5968589474d269818713c86ee5e69a685584d1c62e7satok break; 5978589474d269818713c86ee5e69a685584d1c62e7satok } 5988589474d269818713c86ee5e69a685584d1c62e7satok removeSpellCheckSpan(spellCheckSpan); 5998589474d269818713c86ee5e69a685584d1c62e7satok spellCheckStart = Math.min(spanStart, spellCheckStart); 6008589474d269818713c86ee5e69a685584d1c62e7satok spellCheckEnd = Math.max(spanEnd, spellCheckEnd); 6018589474d269818713c86ee5e69a685584d1c62e7satok } 6028589474d269818713c86ee5e69a685584d1c62e7satok 6038589474d269818713c86ee5e69a685584d1c62e7satok if (DBG) { 6048589474d269818713c86ee5e69a685584d1c62e7satok Log.d(TAG, "addSpellCheckSpan: " 6058589474d269818713c86ee5e69a685584d1c62e7satok + ", End = " + spellCheckEnd + ", Start = " + spellCheckStart 6068589474d269818713c86ee5e69a685584d1c62e7satok + ", next = " + scheduleOtherSpellCheck + "\n" 6078589474d269818713c86ee5e69a685584d1c62e7satok + editable.subSequence(spellCheckStart, spellCheckEnd)); 6088589474d269818713c86ee5e69a685584d1c62e7satok } 6098589474d269818713c86ee5e69a685584d1c62e7satok 6108589474d269818713c86ee5e69a685584d1c62e7satok // Stop spell checking when there are no characters in the range. 6118589474d269818713c86ee5e69a685584d1c62e7satok if (spellCheckEnd < start) { 6128589474d269818713c86ee5e69a685584d1c62e7satok break; 6138589474d269818713c86ee5e69a685584d1c62e7satok } 614a4c82c1c78cdd37e0dea1b5b2f44c25cc584034csatok if (spellCheckEnd <= spellCheckStart) { 615a4c82c1c78cdd37e0dea1b5b2f44c25cc584034csatok break; 616a4c82c1c78cdd37e0dea1b5b2f44c25cc584034csatok } 6178589474d269818713c86ee5e69a685584d1c62e7satok if (createSpellCheckSpan) { 6188589474d269818713c86ee5e69a685584d1c62e7satok addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd); 6198589474d269818713c86ee5e69a685584d1c62e7satok } 6208589474d269818713c86ee5e69a685584d1c62e7satok } while (false); 6218589474d269818713c86ee5e69a685584d1c62e7satok wordStart = spellCheckEnd; 6228898358bfdf4693af02ad454e1deb8034379ce02satok } else { 6238898358bfdf4693af02ad454e1deb8034379ce02satok while (wordStart <= end) { 6248898358bfdf4693af02ad454e1deb8034379ce02satok if (wordEnd >= start && wordEnd > wordStart) { 6258898358bfdf4693af02ad454e1deb8034379ce02satok if (wordCount >= MAX_NUMBER_OF_WORDS) { 6268898358bfdf4693af02ad454e1deb8034379ce02satok scheduleOtherSpellCheck = true; 6278898358bfdf4693af02ad454e1deb8034379ce02satok break; 6288898358bfdf4693af02ad454e1deb8034379ce02satok } 6298898358bfdf4693af02ad454e1deb8034379ce02satok // A new word has been created across the interval boundaries with this 6308898358bfdf4693af02ad454e1deb8034379ce02satok // edit. The previous spans (that ended on start / started on end) are 6318898358bfdf4693af02ad454e1deb8034379ce02satok // not valid anymore and must be removed. 6328898358bfdf4693af02ad454e1deb8034379ce02satok if (wordStart < start && wordEnd > start) { 6338898358bfdf4693af02ad454e1deb8034379ce02satok removeSpansAt(editable, start, spellCheckSpans); 6348898358bfdf4693af02ad454e1deb8034379ce02satok removeSpansAt(editable, start, suggestionSpans); 6358898358bfdf4693af02ad454e1deb8034379ce02satok } 636287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 6378898358bfdf4693af02ad454e1deb8034379ce02satok if (wordStart < end && wordEnd > end) { 6388898358bfdf4693af02ad454e1deb8034379ce02satok removeSpansAt(editable, end, spellCheckSpans); 6398898358bfdf4693af02ad454e1deb8034379ce02satok removeSpansAt(editable, end, suggestionSpans); 6408898358bfdf4693af02ad454e1deb8034379ce02satok } 641287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 6428898358bfdf4693af02ad454e1deb8034379ce02satok // Do not create new boundary spans if they already exist 6438898358bfdf4693af02ad454e1deb8034379ce02satok boolean createSpellCheckSpan = true; 6448898358bfdf4693af02ad454e1deb8034379ce02satok if (wordEnd == start) { 6458898358bfdf4693af02ad454e1deb8034379ce02satok for (int i = 0; i < spellCheckSpans.length; i++) { 6468898358bfdf4693af02ad454e1deb8034379ce02satok final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]); 6478898358bfdf4693af02ad454e1deb8034379ce02satok if (spanEnd == start) { 6488898358bfdf4693af02ad454e1deb8034379ce02satok createSpellCheckSpan = false; 6498898358bfdf4693af02ad454e1deb8034379ce02satok break; 6508898358bfdf4693af02ad454e1deb8034379ce02satok } 651287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 652287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 653287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 6548898358bfdf4693af02ad454e1deb8034379ce02satok if (wordStart == end) { 6558898358bfdf4693af02ad454e1deb8034379ce02satok for (int i = 0; i < spellCheckSpans.length; i++) { 6568898358bfdf4693af02ad454e1deb8034379ce02satok final int spanStart = editable.getSpanStart(spellCheckSpans[i]); 6578898358bfdf4693af02ad454e1deb8034379ce02satok if (spanStart == end) { 6588898358bfdf4693af02ad454e1deb8034379ce02satok createSpellCheckSpan = false; 6598898358bfdf4693af02ad454e1deb8034379ce02satok break; 6608898358bfdf4693af02ad454e1deb8034379ce02satok } 661287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 662287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 663287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 6648898358bfdf4693af02ad454e1deb8034379ce02satok if (createSpellCheckSpan) { 6658898358bfdf4693af02ad454e1deb8034379ce02satok addSpellCheckSpan(editable, wordStart, wordEnd); 6668898358bfdf4693af02ad454e1deb8034379ce02satok } 6678898358bfdf4693af02ad454e1deb8034379ce02satok wordCount++; 668287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 669287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 6708898358bfdf4693af02ad454e1deb8034379ce02satok // iterate word by word 6718898358bfdf4693af02ad454e1deb8034379ce02satok int originalWordEnd = wordEnd; 6728898358bfdf4693af02ad454e1deb8034379ce02satok wordEnd = mWordIterator.following(wordEnd); 6738898358bfdf4693af02ad454e1deb8034379ce02satok if ((wordIteratorWindowEnd < end) && 6748898358bfdf4693af02ad454e1deb8034379ce02satok (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) { 6758898358bfdf4693af02ad454e1deb8034379ce02satok wordIteratorWindowEnd = 6768898358bfdf4693af02ad454e1deb8034379ce02satok Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL); 6778898358bfdf4693af02ad454e1deb8034379ce02satok mWordIterator.setCharSequence( 6788898358bfdf4693af02ad454e1deb8034379ce02satok editable, originalWordEnd, wordIteratorWindowEnd); 6798898358bfdf4693af02ad454e1deb8034379ce02satok wordEnd = mWordIterator.following(originalWordEnd); 6808898358bfdf4693af02ad454e1deb8034379ce02satok } 6818898358bfdf4693af02ad454e1deb8034379ce02satok if (wordEnd == BreakIterator.DONE) break; 6828898358bfdf4693af02ad454e1deb8034379ce02satok wordStart = mWordIterator.getBeginning(wordEnd); 6838898358bfdf4693af02ad454e1deb8034379ce02satok if (wordStart == BreakIterator.DONE) { 6848898358bfdf4693af02ad454e1deb8034379ce02satok break; 6858898358bfdf4693af02ad454e1deb8034379ce02satok } 686287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 687287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 688287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 689287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne if (scheduleOtherSpellCheck) { 690e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne // Update range span: start new spell check from last wordStart 691e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne setRangeSpan(editable, wordStart, end); 692287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } else { 693e9b82808d412fa1b87954dd88579b92d0b4ab0e2Gilles Debunne removeRangeSpan(editable); 694287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 695287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 696287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne spellCheck(); 697287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 698287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne 699f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne private <T> void removeSpansAt(Editable editable, int offset, T[] spans) { 700287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne final int length = spans.length; 701287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne for (int i = 0; i < length; i++) { 702287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne final T span = spans[i]; 703f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne final int start = editable.getSpanStart(span); 704287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne if (start > offset) continue; 705f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne final int end = editable.getSpanEnd(span); 706287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne if (end < offset) continue; 707f656030a865be029c7c29bab2ea6f5fff07624aeGilles Debunne editable.removeSpan(span); 708287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 709287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 710287d6c6e12a38864d019fa7b9184206bc8a31ea1Gilles Debunne } 7116435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne} 712