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