SpellChecker.java revision e1fc4f6c3c7d573f013b707ee962d58f9fb636dd
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; 226435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.text.Spanned; 236435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.text.style.SpellCheckSpan; 246435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.text.style.SuggestionSpan; 256435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.SpellCheckerSession; 266435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; 276435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.SuggestionsInfo; 286435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.TextInfo; 296435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport android.view.textservice.TextServicesManager; 306435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 316435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunneimport com.android.internal.util.ArrayUtils; 326435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 336435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 346435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne/** 356435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne * Helper class for TextView. Bridge between the TextView and the Dictionnary service. 366435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne * 376435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne * @hide 386435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne */ 396435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunnepublic class SpellChecker implements SpellCheckerSessionListener { 406435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 416435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private final TextView mTextView; 426435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 430eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne final SpellCheckerSession mSpellCheckerSession; 446435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final int mCookie; 456435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 46b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated 47b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne // SpellCheckSpan has been recycled and can be-reused. 48b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne // May contain null SpellCheckSpans after a given index. 496435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private int[] mIds; 506435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private SpellCheckSpan[] mSpellCheckSpans; 51b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne // The mLength first elements of the above arrays have been initialized 526435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private int mLength; 536435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 546435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne private int mSpanSequenceCounter = 0; 556435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 566435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne public SpellChecker(TextView textView) { 576435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mTextView = textView; 586435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 596435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final TextServicesManager textServicesManager = (TextServicesManager) textView.getContext(). 606435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); 610eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne mSpellCheckerSession = textServicesManager.newSpellCheckerSession( 6270deff4c107963164f8b88365909fd30ab5e6526satok null /* not currently used by the textServicesManager */, 6370deff4c107963164f8b88365909fd30ab5e6526satok null /* null locale means use the languages defined in Settings 6470deff4c107963164f8b88365909fd30ab5e6526satok if referToSpellCheckerLanguageSettings is true */, 656435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne this, true /* means use the languages defined in Settings */); 666435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mCookie = hashCode(); 676435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 686435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne // Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand 69b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne final int size = ArrayUtils.idealObjectArraySize(1); 706435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mIds = new int[size]; 716435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mSpellCheckSpans = new SpellCheckSpan[size]; 726435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mLength = 0; 736435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 746435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 75186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne /** 76186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne * @return true if a spell checker session has successfully been created. Returns false if not, 77186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne * for instance when spell checking has been disabled in settings. 78186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne */ 79186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne public boolean isSessionActive() { 80186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne return mSpellCheckerSession != null; 81186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne } 82186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne 83186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne public void closeSession() { 84186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne if (mSpellCheckerSession != null) { 85186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne mSpellCheckerSession.close(); 86186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne } 87186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne } 88186aaf973530f426b9b0e602e9744c591aa4aea9Gilles Debunne 89b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne private int nextSpellCheckSpanIndex() { 90b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne for (int i = 0; i < mLength; i++) { 91b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne if (mIds[i] < 0) return i; 92b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne } 93b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne 94b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne if (mLength == mSpellCheckSpans.length) { 95b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne final int newSize = mLength * 2; 966435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne int[] newIds = new int[newSize]; 976435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne SpellCheckSpan[] newSpellCheckSpans = new SpellCheckSpan[newSize]; 98b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne System.arraycopy(mIds, 0, newIds, 0, mLength); 99b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne System.arraycopy(mSpellCheckSpans, 0, newSpellCheckSpans, 0, mLength); 1006435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mIds = newIds; 1016435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mSpellCheckSpans = newSpellCheckSpans; 1026435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1036435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 104b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne mSpellCheckSpans[mLength] = new SpellCheckSpan(); 1056435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne mLength++; 106b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne return mLength - 1; 107b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne } 1086435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 109b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne public void addSpellCheckSpan(int wordStart, int wordEnd) { 110b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne final int index = nextSpellCheckSpanIndex(); 111b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne ((Editable) mTextView.getText()).setSpan(mSpellCheckSpans[index], wordStart, wordEnd, 112b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 113b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne mIds[index] = mSpanSequenceCounter++; 1146435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1156435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1166435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne public void removeSpellCheckSpan(SpellCheckSpan spellCheckSpan) { 1176435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne for (int i = 0; i < mLength; i++) { 1186435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (mSpellCheckSpans[i] == spellCheckSpan) { 119b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne mSpellCheckSpans[i].setSpellCheckInProgress(false); 120b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne mIds[i] = -1; 1216435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne return; 1226435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1236435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1246435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1256435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1266435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne public void onSelectionChanged() { 127b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne spellCheck(); 1286435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1296435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 130b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne public void spellCheck() { 1310eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne if (mSpellCheckerSession == null) return; 1329906847cef4307896a64c68fa27da6603a7d8da2Gilles Debunne 1336435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final Editable editable = (Editable) mTextView.getText(); 1346435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final int selectionStart = Selection.getSelectionStart(editable); 1356435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final int selectionEnd = Selection.getSelectionEnd(editable); 1366435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1376435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne TextInfo[] textInfos = new TextInfo[mLength]; 1386435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne int textInfosCount = 0; 1396435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1406435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne for (int i = 0; i < mLength; i++) { 141b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i]; 1426435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (spellCheckSpan.isSpellCheckInProgress()) continue; 1436435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1446435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final int start = editable.getSpanStart(spellCheckSpan); 1456435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final int end = editable.getSpanEnd(spellCheckSpan); 1466435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1476435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne // Do not check this word if the user is currently editing it 148d6e3494421dff2a091f1011e5266b280b2109843Gilles Debunne if (start >= 0 && end > start && (selectionEnd < start || selectionStart > end)) { 1496435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final String word = editable.subSequence(start, end).toString(); 150b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne spellCheckSpan.setSpellCheckInProgress(true); 1516435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne textInfos[textInfosCount++] = new TextInfo(word, mCookie, mIds[i]); 1526435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1536435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1546435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1556435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (textInfosCount > 0) { 1566435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (textInfosCount < mLength) { 1576435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne TextInfo[] textInfosCopy = new TextInfo[textInfosCount]; 1586435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne System.arraycopy(textInfos, 0, textInfosCopy, 0, textInfosCount); 1596435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne textInfos = textInfosCopy; 1606435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1610eea6681519277310e1733d791bfc0342b8e5ceaGilles Debunne mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE, 1626435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne false /* TODO Set sequentialWords to true for initial spell check */); 1636435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1646435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1656435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 1666435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne @Override 1676435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne public void onGetSuggestions(SuggestionsInfo[] results) { 168e1fc4f6c3c7d573f013b707ee962d58f9fb636ddGilles Debunne final Editable editable = (Editable) mTextView.getText(); 1696435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne for (int i = 0; i < results.length; i++) { 1706435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne SuggestionsInfo suggestionsInfo = results[i]; 1716435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (suggestionsInfo.getCookie() != mCookie) continue; 1726435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final int sequenceNumber = suggestionsInfo.getSequence(); 173b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne 174b062e81e3a16af43db3619d721aa522c137d1aa9Gilles Debunne for (int j = 0; j < mLength; j++) { 1756435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (sequenceNumber == mIds[j]) { 1766435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne final int attributes = suggestionsInfo.getSuggestionsAttributes(); 1776435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne boolean isInDictionary = 1786435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0); 1796435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne boolean looksLikeTypo = 1806435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0); 1816435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 182e1fc4f6c3c7d573f013b707ee962d58f9fb636ddGilles Debunne SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j]; 1836435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne if (!isInDictionary && looksLikeTypo) { 184e1fc4f6c3c7d573f013b707ee962d58f9fb636ddGilles Debunne createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan); 1856435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 186e1fc4f6c3c7d573f013b707ee962d58f9fb636ddGilles Debunne editable.removeSpan(spellCheckSpan); 187176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne break; 1886435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1896435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1906435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1916435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 1926435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne 193e1fc4f6c3c7d573f013b707ee962d58f9fb636ddGilles Debunne private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo, 194176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne SpellCheckSpan spellCheckSpan) { 195176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne final int start = editable.getSpanStart(spellCheckSpan); 196176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne final int end = editable.getSpanEnd(spellCheckSpan); 197176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 198176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne // Other suggestion spans may exist on that region, with identical suggestions, filter 199176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne // them out to avoid duplicates. First, filter suggestion spans on that exact region. 200176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne SuggestionSpan[] suggestionSpans = editable.getSpans(start, end, SuggestionSpan.class); 201176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne final int length = suggestionSpans.length; 202176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne for (int i = 0; i < length; i++) { 203176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne final int spanStart = editable.getSpanStart(suggestionSpans[i]); 204176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne final int spanEnd = editable.getSpanEnd(suggestionSpans[i]); 205176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne if (spanStart != start || spanEnd != end) { 206176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne suggestionSpans[i] = null; 207176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne break; 208176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } 209176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } 210176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 211176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne final int suggestionsCount = suggestionsInfo.getSuggestionsCount(); 212176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne String[] suggestions; 213176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne if (suggestionsCount <= 0) { 214176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne // A negative suggestion count is possible 215176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne suggestions = ArrayUtils.emptyArray(String.class); 216176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } else { 217176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne int numberOfSuggestions = 0; 218176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne suggestions = new String[suggestionsCount]; 219176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 220176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne for (int i = 0; i < suggestionsCount; i++) { 221176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne final String spellSuggestion = suggestionsInfo.getSuggestionAt(i); 222176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne if (spellSuggestion == null) break; 223176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne boolean suggestionFound = false; 224176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 225176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne for (int j = 0; j < length && !suggestionFound; j++) { 226176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne if (suggestionSpans[j] == null) break; 227176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 228176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne String[] suggests = suggestionSpans[j].getSuggestions(); 229176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne for (int k = 0; k < suggests.length; k++) { 230176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne if (spellSuggestion.equals(suggests[k])) { 231176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne // The suggestion is already provided by an other SuggestionSpan 232176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne suggestionFound = true; 233176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne break; 234176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } 235176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } 236176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } 237176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 238176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne if (!suggestionFound) { 239176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne suggestions[numberOfSuggestions++] = spellSuggestion; 240176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } 241176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } 242176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 243176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne if (numberOfSuggestions != suggestionsCount) { 244176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne String[] newSuggestions = new String[numberOfSuggestions]; 245176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne System.arraycopy(suggestions, 0, newSuggestions, 0, numberOfSuggestions); 246176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne suggestions = newSuggestions; 247176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne } 2486435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 249176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 250176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions, 251176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED); 252176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 253176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne 254176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne // TODO limit to the word rectangle region 255176cd0d3b4a612b9fdd88c1d245e9fd93a327bf2Gilles Debunne mTextView.invalidate(); 2566435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne } 2576435a56a8c02de98befcc8cd743b2b638cffb327Gilles Debunne} 258