AndroidSpellCheckerService.java revision 51075d145a85d1acaff08c02f4d6b10b175eaa36
1022c1cc20379767966f4915e2dea65fc0b67c0d8satok/*
2022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Copyright (C) 2011 The Android Open Source Project
3022c1cc20379767966f4915e2dea65fc0b67c0d8satok *
4022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5022c1cc20379767966f4915e2dea65fc0b67c0d8satok * use this file except in compliance with the License. You may obtain a copy of
6022c1cc20379767966f4915e2dea65fc0b67c0d8satok * the License at
7022c1cc20379767966f4915e2dea65fc0b67c0d8satok *
8022c1cc20379767966f4915e2dea65fc0b67c0d8satok * http://www.apache.org/licenses/LICENSE-2.0
9022c1cc20379767966f4915e2dea65fc0b67c0d8satok *
10022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Unless required by applicable law or agreed to in writing, software
11022c1cc20379767966f4915e2dea65fc0b67c0d8satok * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12022c1cc20379767966f4915e2dea65fc0b67c0d8satok * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13022c1cc20379767966f4915e2dea65fc0b67c0d8satok * License for the specific language governing permissions and limitations under
14022c1cc20379767966f4915e2dea65fc0b67c0d8satok * the License.
15022c1cc20379767966f4915e2dea65fc0b67c0d8satok */
16022c1cc20379767966f4915e2dea65fc0b67c0d8satok
17022c1cc20379767966f4915e2dea65fc0b67c0d8satokpackage com.android.inputmethod.latin.spellcheck;
18022c1cc20379767966f4915e2dea65fc0b67c0d8satok
19c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalardimport android.content.Intent;
203234123fba901243990972158d023a5d1c273316Jean Chalardimport android.content.res.Resources;
21022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.service.textservice.SpellCheckerService;
22ab72a97d7ce44230a0c824797d1675a5ca354a56Tadashi G. Takaokaimport android.text.TextUtils;
23a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalardimport android.util.Log;
24022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.view.textservice.SuggestionsInfo;
25022c1cc20379767966f4915e2dea65fc0b67c0d8satokimport android.view.textservice.TextInfo;
26022c1cc20379767966f4915e2dea65fc0b67c0d8satok
273234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.compat.ArraysCompatUtils;
283234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo;
29673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalardimport com.android.inputmethod.latin.BinaryDictionary;
303234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.Dictionary;
313234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.Dictionary.DataType;
323234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.Dictionary.WordCallback;
33150bad6fd4b401177c480acf5640b4db0f821886Jean Chalardimport com.android.inputmethod.latin.DictionaryCollection;
343234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.DictionaryFactory;
35673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalardimport com.android.inputmethod.latin.Flag;
36ef35cb631c45c8b106fe7ed9e0d1178c3e5fb963Jean Chalardimport com.android.inputmethod.latin.LocaleUtils;
3759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalardimport com.android.inputmethod.latin.R;
38f019d505d7da97c03c321eef02c4879c4e0448f6Jean Chalardimport com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
393234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.Utils;
403234123fba901243990972158d023a5d1c273316Jean Chalardimport com.android.inputmethod.latin.WordComposer;
413234123fba901243990972158d023a5d1c273316Jean Chalard
426b166a193398554694cb680f704c2ffc23d03a0eJean Chalardimport java.util.ArrayList;
43f098fbbef324df034cc04de04d9b5fe6657238c7Jean Chalardimport java.util.Arrays;
443234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Collections;
453234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Locale;
463234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.Map;
473234123fba901243990972158d023a5d1c273316Jean Chalardimport java.util.TreeMap;
483234123fba901243990972158d023a5d1c273316Jean Chalard
49022c1cc20379767966f4915e2dea65fc0b67c0d8satok/**
50022c1cc20379767966f4915e2dea65fc0b67c0d8satok * Service for spell checking, using LatinIME's dictionaries and mechanisms.
51022c1cc20379767966f4915e2dea65fc0b67c0d8satok */
52022c1cc20379767966f4915e2dea65fc0b67c0d8satokpublic class AndroidSpellCheckerService extends SpellCheckerService {
53a90992e56244a914195daba3a2dd8a0e66e63384satok    private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
54a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard    private static final boolean DBG = false;
55a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard    private static final int POOL_SIZE = 2;
563234123fba901243990972158d023a5d1c273316Jean Chalard
57f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard    private static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
58f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard    private static final int CAPITALIZE_FIRST = 1; // First only
59f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard    private static final int CAPITALIZE_ALL = 2; // All caps
60f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard
616b166a193398554694cb680f704c2ffc23d03a0eJean Chalard    private final static String[] EMPTY_STRING_ARRAY = new String[0];
62e897e4d3422c8d9d8b6f051376cc2ba16e4d5945Jean Chalard    private final static SuggestionsInfo NOT_IN_DICT_EMPTY_SUGGESTIONS =
636b166a193398554694cb680f704c2ffc23d03a0eJean Chalard            new SuggestionsInfo(0, EMPTY_STRING_ARRAY);
64e897e4d3422c8d9d8b6f051376cc2ba16e4d5945Jean Chalard    private final static SuggestionsInfo IN_DICT_EMPTY_SUGGESTIONS =
65e897e4d3422c8d9d8b6f051376cc2ba16e4d5945Jean Chalard            new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY,
66e897e4d3422c8d9d8b6f051376cc2ba16e4d5945Jean Chalard                    EMPTY_STRING_ARRAY);
67673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard    private final static Flag[] USE_FULL_EDIT_DISTANCE_FLAG_ARRAY;
68673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard    static {
69673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard        // See BinaryDictionary.java for an explanation of these flags
70673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard        // Specifially, ALL_CONFIG_FLAGS means that we want to consider all flags with the
71673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard        // current dictionary configuration - for example, consider the UMLAUT flag
72673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard        // so that it will be turned on for German dictionaries and off for others.
73673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard        USE_FULL_EDIT_DISTANCE_FLAG_ARRAY = Arrays.copyOf(BinaryDictionary.ALL_CONFIG_FLAGS,
74673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard                BinaryDictionary.ALL_CONFIG_FLAGS.length + 1);
75673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard        USE_FULL_EDIT_DISTANCE_FLAG_ARRAY[BinaryDictionary.ALL_CONFIG_FLAGS.length] =
76673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard                BinaryDictionary.FLAG_USE_FULL_EDIT_DISTANCE;
77673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard    }
78c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard    private Map<String, DictionaryPool> mDictionaryPools =
79a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard            Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
80150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard    private Map<String, Dictionary> mUserDictionaries =
81150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard            Collections.synchronizedMap(new TreeMap<String, Dictionary>());
823234123fba901243990972158d023a5d1c273316Jean Chalard
834609c02f9e61370557fee675c67263160fbf7feeJean Chalard    // The threshold for a candidate to be offered as a suggestion.
844609c02f9e61370557fee675c67263160fbf7feeJean Chalard    private double mSuggestionThreshold;
854609c02f9e61370557fee675c67263160fbf7feeJean Chalard    // The threshold for a suggestion to be considered "likely".
864609c02f9e61370557fee675c67263160fbf7feeJean Chalard    private double mLikelyThreshold;
8759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
8859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard    @Override public void onCreate() {
8959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        super.onCreate();
904609c02f9e61370557fee675c67263160fbf7feeJean Chalard        mSuggestionThreshold =
914609c02f9e61370557fee675c67263160fbf7feeJean Chalard                Double.parseDouble(getString(R.string.spellchecker_suggestion_threshold_value));
924609c02f9e61370557fee675c67263160fbf7feeJean Chalard        mLikelyThreshold =
934609c02f9e61370557fee675c67263160fbf7feeJean Chalard                Double.parseDouble(getString(R.string.spellchecker_likely_threshold_value));
9459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard    }
9559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
965bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    @Override
975bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    public Session createSession() {
9859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        return new AndroidSpellCheckerSession(this);
995bcf8ee66ceb38675a6b70fefcb574978e0fae92satok    }
1005bcf8ee66ceb38675a6b70fefcb574978e0fae92satok
1013234123fba901243990972158d023a5d1c273316Jean Chalard    private static class SuggestionsGatherer implements WordCallback {
10259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        public static class Result {
10359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard            public final String[] mSuggestions;
104647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard            public final boolean mHasLikelySuggestions;
105647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard            public Result(final String[] gatheredSuggestions, final boolean hasLikelySuggestions) {
10659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                mSuggestions = gatheredSuggestions;
107647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                mHasLikelySuggestions = hasLikelySuggestions;
10859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard            }
10959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        }
11059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
1116b166a193398554694cb680f704c2ffc23d03a0eJean Chalard        private final ArrayList<CharSequence> mSuggestions;
1123234123fba901243990972158d023a5d1c273316Jean Chalard        private final int[] mScores;
11385782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard        private final String mOriginalText;
1144609c02f9e61370557fee675c67263160fbf7feeJean Chalard        private final double mSuggestionThreshold;
1154609c02f9e61370557fee675c67263160fbf7feeJean Chalard        private final double mLikelyThreshold;
1163234123fba901243990972158d023a5d1c273316Jean Chalard        private final int mMaxLength;
1173234123fba901243990972158d023a5d1c273316Jean Chalard        private int mLength = 0;
11859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
11959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        // The two following attributes are only ever filled if the requested max length
12059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        // is 0 (or less, which is treated the same).
12159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        private String mBestSuggestion = null;
12259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        private int mBestScore = Integer.MIN_VALUE; // As small as possible
1233234123fba901243990972158d023a5d1c273316Jean Chalard
1244609c02f9e61370557fee675c67263160fbf7feeJean Chalard        SuggestionsGatherer(final String originalText, final double suggestionThreshold,
1254609c02f9e61370557fee675c67263160fbf7feeJean Chalard                final double likelyThreshold, final int maxLength) {
12685782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard            mOriginalText = originalText;
1274609c02f9e61370557fee675c67263160fbf7feeJean Chalard            mSuggestionThreshold = suggestionThreshold;
1284609c02f9e61370557fee675c67263160fbf7feeJean Chalard            mLikelyThreshold = likelyThreshold;
1293234123fba901243990972158d023a5d1c273316Jean Chalard            mMaxLength = maxLength;
1306b166a193398554694cb680f704c2ffc23d03a0eJean Chalard            mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
1313234123fba901243990972158d023a5d1c273316Jean Chalard            mScores = new int[mMaxLength];
1323234123fba901243990972158d023a5d1c273316Jean Chalard        }
1333234123fba901243990972158d023a5d1c273316Jean Chalard
1343234123fba901243990972158d023a5d1c273316Jean Chalard        @Override
1353234123fba901243990972158d023a5d1c273316Jean Chalard        synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
1363234123fba901243990972158d023a5d1c273316Jean Chalard                int dicTypeId, DataType dataType) {
1373234123fba901243990972158d023a5d1c273316Jean Chalard            final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score);
1383234123fba901243990972158d023a5d1c273316Jean Chalard            // binarySearch returns the index if the element exists, and -<insertion index> - 1
1393234123fba901243990972158d023a5d1c273316Jean Chalard            // if it doesn't. See documentation for binarySearch.
1403234123fba901243990972158d023a5d1c273316Jean Chalard            final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
1413234123fba901243990972158d023a5d1c273316Jean Chalard
1424609c02f9e61370557fee675c67263160fbf7feeJean Chalard            if (insertIndex == 0 && mLength >= mMaxLength) {
1434609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // In the future, we may want to keep track of the best suggestion score even if
1444609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // we are asked for 0 suggestions. In this case, we can use the following
1454609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // (tested) code to keep it:
1464609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // If the maxLength is 0 (should never be less, but if it is, it's treated as 0)
1474609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // then we need to keep track of the best suggestion in mBestScore and
1484609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // mBestSuggestion. This is so that we know whether the best suggestion makes
1494609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // the score cutoff, since we need to know that to return a meaningful
1504609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // looksLikeTypo.
1514609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // if (0 >= mMaxLength) {
1524609c02f9e61370557fee675c67263160fbf7feeJean Chalard                //     if (score > mBestScore) {
1534609c02f9e61370557fee675c67263160fbf7feeJean Chalard                //         mBestScore = score;
1544609c02f9e61370557fee675c67263160fbf7feeJean Chalard                //         mBestSuggestion = new String(word, wordOffset, wordLength);
1554609c02f9e61370557fee675c67263160fbf7feeJean Chalard                //     }
1564609c02f9e61370557fee675c67263160fbf7feeJean Chalard                // }
1574609c02f9e61370557fee675c67263160fbf7feeJean Chalard                return true;
1584609c02f9e61370557fee675c67263160fbf7feeJean Chalard            }
1594609c02f9e61370557fee675c67263160fbf7feeJean Chalard
1604609c02f9e61370557fee675c67263160fbf7feeJean Chalard            // Compute the normalized score and skip this word if it's normalized score does not
1614609c02f9e61370557fee675c67263160fbf7feeJean Chalard            // make the threshold.
1624609c02f9e61370557fee675c67263160fbf7feeJean Chalard            final String wordString = new String(word, wordOffset, wordLength);
1634609c02f9e61370557fee675c67263160fbf7feeJean Chalard            final double normalizedScore =
1644609c02f9e61370557fee675c67263160fbf7feeJean Chalard                    Utils.calcNormalizedScore(mOriginalText, wordString, score);
1654609c02f9e61370557fee675c67263160fbf7feeJean Chalard            if (normalizedScore < mSuggestionThreshold) {
1664609c02f9e61370557fee675c67263160fbf7feeJean Chalard                if (DBG) Log.i(TAG, wordString + " does not make the score threshold");
1674609c02f9e61370557fee675c67263160fbf7feeJean Chalard                return true;
1684609c02f9e61370557fee675c67263160fbf7feeJean Chalard            }
1694609c02f9e61370557fee675c67263160fbf7feeJean Chalard
1703234123fba901243990972158d023a5d1c273316Jean Chalard            if (mLength < mMaxLength) {
1713234123fba901243990972158d023a5d1c273316Jean Chalard                final int copyLen = mLength - insertIndex;
1723234123fba901243990972158d023a5d1c273316Jean Chalard                ++mLength;
1733234123fba901243990972158d023a5d1c273316Jean Chalard                System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
1744609c02f9e61370557fee675c67263160fbf7feeJean Chalard                mSuggestions.add(insertIndex, wordString);
1753234123fba901243990972158d023a5d1c273316Jean Chalard            } else {
1763234123fba901243990972158d023a5d1c273316Jean Chalard                System.arraycopy(mScores, 1, mScores, 0, insertIndex);
1774609c02f9e61370557fee675c67263160fbf7feeJean Chalard                mSuggestions.add(insertIndex, wordString);
1786b166a193398554694cb680f704c2ffc23d03a0eJean Chalard                mSuggestions.remove(0);
1793234123fba901243990972158d023a5d1c273316Jean Chalard            }
1803234123fba901243990972158d023a5d1c273316Jean Chalard            mScores[insertIndex] = score;
1813234123fba901243990972158d023a5d1c273316Jean Chalard
1823234123fba901243990972158d023a5d1c273316Jean Chalard            return true;
1833234123fba901243990972158d023a5d1c273316Jean Chalard        }
1843234123fba901243990972158d023a5d1c273316Jean Chalard
18585782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard        public Result getResults(final int capitalizeType, final Locale locale) {
18659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard            final String[] gatheredSuggestions;
187647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard            final boolean hasLikelySuggestions;
18859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard            if (0 == mLength) {
18959b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                // Either we found no suggestions, or we found some BUT the max length was 0.
19059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                // If we found some mBestSuggestion will not be null. If it is null, then
19159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                // we found none, regardless of the max length.
19259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                if (null == mBestSuggestion) {
19359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                    gatheredSuggestions = null;
194647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                    hasLikelySuggestions = false;
19559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                } else {
19659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                    gatheredSuggestions = EMPTY_STRING_ARRAY;
19759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                    final double normalizedScore =
19885782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard                            Utils.calcNormalizedScore(mOriginalText, mBestSuggestion, mBestScore);
1994609c02f9e61370557fee675c67263160fbf7feeJean Chalard                    hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
20059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                }
20159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard            } else {
20259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                if (DBG) {
20359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                    if (mLength != mSuggestions.size()) {
20459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                        Log.e(TAG, "Suggestion size is not the same as stored mLength");
20559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                    }
206af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard                    for (int i = mLength - 1; i >= 0; --i) {
207af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard                        Log.i(TAG, "" + mScores[i] + " " + mSuggestions.get(i));
208af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard                    }
2096b166a193398554694cb680f704c2ffc23d03a0eJean Chalard                }
21059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                Collections.reverse(mSuggestions);
21159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                Utils.removeDupes(mSuggestions);
212f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                if (CAPITALIZE_ALL == capitalizeType) {
213f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                    for (int i = 0; i < mSuggestions.size(); ++i) {
214f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                        // get(i) returns a CharSequence which is actually a String so .toString()
215f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                        // should return the same object.
216f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                        mSuggestions.set(i, mSuggestions.get(i).toString().toUpperCase(locale));
217f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                    }
218f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                } else if (CAPITALIZE_FIRST == capitalizeType) {
219f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                    for (int i = 0; i < mSuggestions.size(); ++i) {
220f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                        // Likewise
221f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                        mSuggestions.set(i, Utils.toTitleCase(mSuggestions.get(i).toString(),
222f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                                locale));
223f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                    }
224f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard                }
22559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                // This returns a String[], while toArray() returns an Object[] which cannot be cast
22659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                // into a String[].
22759b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY);
22859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
229af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard                final int bestScore = mScores[mLength - 1];
23059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                final CharSequence bestSuggestion = mSuggestions.get(0);
23159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard                final double normalizedScore =
23285782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard                        Utils.calcNormalizedScore(mOriginalText, bestSuggestion, bestScore);
2334609c02f9e61370557fee675c67263160fbf7feeJean Chalard                hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
234af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard                if (DBG) {
235af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard                    Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
2364609c02f9e61370557fee675c67263160fbf7feeJean Chalard                    Log.i(TAG, "Normalized score = " + normalizedScore
2374609c02f9e61370557fee675c67263160fbf7feeJean Chalard                            + " (threshold " + mLikelyThreshold
238647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                            + ") => hasLikelySuggestions = " + hasLikelySuggestions);
239af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard                }
2403234123fba901243990972158d023a5d1c273316Jean Chalard            }
241647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard            return new Result(gatheredSuggestions, hasLikelySuggestions);
2423234123fba901243990972158d023a5d1c273316Jean Chalard        }
2433234123fba901243990972158d023a5d1c273316Jean Chalard    }
2443234123fba901243990972158d023a5d1c273316Jean Chalard
245c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard    @Override
246c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard    public boolean onUnbind(final Intent intent) {
247c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard        final Map<String, DictionaryPool> oldPools = mDictionaryPools;
248c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard        mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
249150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        final Map<String, Dictionary> oldUserDictionaries = mUserDictionaries;
250150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        mUserDictionaries = Collections.synchronizedMap(new TreeMap<String, Dictionary>());
251c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard        for (DictionaryPool pool : oldPools.values()) {
252c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard            pool.close();
253c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard        }
254150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        for (Dictionary dict : oldUserDictionaries.values()) {
255150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard            dict.close();
256150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        }
257c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard        return false;
258c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard    }
259c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard
260a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard    private DictionaryPool getDictionaryPool(final String locale) {
261a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard        DictionaryPool pool = mDictionaryPools.get(locale);
262a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard        if (null == pool) {
263ef35cb631c45c8b106fe7ed9e0d1178c3e5fb963Jean Chalard            final Locale localeObject = LocaleUtils.constructLocaleFromString(locale);
264a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard            pool = new DictionaryPool(POOL_SIZE, this, localeObject);
265a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard            mDictionaryPools.put(locale, pool);
2663234123fba901243990972158d023a5d1c273316Jean Chalard        }
267a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard        return pool;
268a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard    }
269a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard
270a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard    public DictAndProximity createDictAndProximity(final Locale locale) {
271a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard        final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo();
272a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard        final Resources resources = getResources();
273a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard        final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
274150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        final DictionaryCollection dictionaryCollection =
275673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard                DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId,
276673cebf9e97289b3b0cd343ff7193dff69684a48Jean Chalard                        USE_FULL_EDIT_DISTANCE_FLAG_ARRAY);
277150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        final String localeStr = locale.toString();
278150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        Dictionary userDict = mUserDictionaries.get(localeStr);
279150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        if (null == userDict) {
280cfed2739221105163893cecdc9402cd9ddc0ab93Jean Chalard            userDict = new SynchronouslyLoadedUserDictionary(this, localeStr, true);
281150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard            mUserDictionaries.put(localeStr, userDict);
282150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        }
283150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        dictionaryCollection.addDictionary(userDict);
284150bad6fd4b401177c480acf5640b4db0f821886Jean Chalard        return new DictAndProximity(dictionaryCollection, proximityInfo);
2853234123fba901243990972158d023a5d1c273316Jean Chalard    }
2863234123fba901243990972158d023a5d1c273316Jean Chalard
287f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard    // This method assumes the text is not empty or null.
288f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard    private static int getCapitalizationType(String text) {
289f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        // If the first char is not uppercase, then the word is either all lower case,
290f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        // and in either case we return CAPITALIZE_NONE.
291f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE;
292f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        final int len = text.codePointCount(0, text.length());
293f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        int capsCount = 1;
294f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        for (int i = 1; i < len; ++i) {
295f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard            if (1 != capsCount && i != capsCount) break;
296f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard            if (Character.isUpperCase(text.codePointAt(i))) ++capsCount;
297f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        }
298f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        // We know the first char is upper case. So we want to test if either everything
299f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        // else is lower case, or if everything else is upper case. If the string is
300f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        // exactly one char long, then we will arrive here with capsCount 1, and this is
301f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        // correct, too.
302f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        if (1 == capsCount) return CAPITALIZE_FIRST;
303f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard        return (len == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
304f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard    }
305f5ef30dfc6f4e436d35c38b6f7e32fbd24d54aabJean Chalard
30659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard    private static class AndroidSpellCheckerSession extends Session {
307a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard        // Immutable, but need the locale which is not available in the constructor yet
30859b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        private DictionaryPool mDictionaryPool;
3095d4c5692f11958064ba7c0de5715f30c96175400Jean Chalard        // Likewise
31059b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        private Locale mLocale;
31159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
31259b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        private final AndroidSpellCheckerService mService;
31359b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard
31459b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        AndroidSpellCheckerSession(final AndroidSpellCheckerService service) {
31559b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard            mService = service;
31659b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard        }
317a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard
3185bcf8ee66ceb38675a6b70fefcb574978e0fae92satok        @Override
3195bcf8ee66ceb38675a6b70fefcb574978e0fae92satok        public void onCreate() {
3205d4c5692f11958064ba7c0de5715f30c96175400Jean Chalard            final String localeString = getLocale();
32159b501a05078e5a9de7cdace19c51ca693076a17Jean Chalard            mDictionaryPool = mService.getDictionaryPool(localeString);
322ef35cb631c45c8b106fe7ed9e0d1178c3e5fb963Jean Chalard            mLocale = LocaleUtils.constructLocaleFromString(localeString);
323a90992e56244a914195daba3a2dd8a0e66e63384satok        }
3243234123fba901243990972158d023a5d1c273316Jean Chalard
32588fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard        /**
32688fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard         * Finds out whether a particular string should be filtered out of spell checking.
32788fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard         *
32888fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard         * This will loosely match URLs, numbers, symbols.
32988fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard         *
33088fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard         * @param text the string to evaluate.
33188fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard         * @return true if we should filter this text out, false otherwise
33288fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard         */
33388fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard        private boolean shouldFilterOut(final String text) {
33488fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
33588fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard
33688fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            // TODO: check if an equivalent processing can't be done more quickly with a
33788fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            // compiled regexp.
33888fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            // Filter by first letter
33988fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            final int firstCodePoint = text.codePointAt(0);
34088fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            // Filter out words that don't start with a letter or an apostrophe
34188fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            if (!Character.isLetter(firstCodePoint)
34288fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard                    && '\'' != firstCodePoint) return true;
34388fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard
34488fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            // Filter contents
34588fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            final int length = text.length();
34688fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            int letterCount = 0;
34788fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            for (int i = 0; i < length; ++i) {
34888fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard                final int codePoint = text.codePointAt(i);
34988fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard                // Any word containing a '@' is probably an e-mail address
35088fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard                // Any word containing a '/' is probably either an ad-hoc combination of two
35188fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard                // words or a URI - in either case we don't want to spell check that
35288fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard                if ('@' == codePoint
35388fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard                        || '/' == codePoint) return true;
35488fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard                if (Character.isLetter(codePoint)) ++letterCount;
35588fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            }
35688fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            // Guestimate heuristic: perform spell checking if at least 3/4 of the characters
35788fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            // in this word are letters
35888fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard            return (letterCount * 4 < length * 3);
35988fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard        }
36088fa53b840686bb428b932eed7dd38162ae902c2Jean Chalard
3615bcf8ee66ceb38675a6b70fefcb574978e0fae92satok        // Note : this must be reentrant
3625bcf8ee66ceb38675a6b70fefcb574978e0fae92satok        /**
3635bcf8ee66ceb38675a6b70fefcb574978e0fae92satok         * Gets a list of suggestions for a specific string. This returns a list of possible
36470b9c5d9913b676f21fe29f795bdb25324509205Jean Chalard         * corrections for the text passed as an argument. It may split or group words, and
3655bcf8ee66ceb38675a6b70fefcb574978e0fae92satok         * even perform grammatical analysis.
3665bcf8ee66ceb38675a6b70fefcb574978e0fae92satok         */
3675bcf8ee66ceb38675a6b70fefcb574978e0fae92satok        @Override
3685bcf8ee66ceb38675a6b70fefcb574978e0fae92satok        public SuggestionsInfo onGetSuggestions(final TextInfo textInfo,
3695bcf8ee66ceb38675a6b70fefcb574978e0fae92satok                final int suggestionsLimit) {
370a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard            try {
371199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                final String text = textInfo.getText();
372199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard
373e897e4d3422c8d9d8b6f051376cc2ba16e4d5945Jean Chalard                if (shouldFilterOut(text)) {
374a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    DictAndProximity dictInfo = null;
375a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    try {
376a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        dictInfo = mDictionaryPool.takeOrGetNull();
377a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        if (null == dictInfo) return NOT_IN_DICT_EMPTY_SUGGESTIONS;
378a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        return dictInfo.mDictionary.isValidWord(text) ? IN_DICT_EMPTY_SUGGESTIONS
379a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                                : NOT_IN_DICT_EMPTY_SUGGESTIONS;
380a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    } finally {
381a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        if (null != dictInfo) {
382a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                            if (!mDictionaryPool.offer(dictInfo)) {
383a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                                Log.e(TAG, "Can't re-insert a dictionary into its pool");
384a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                            }
385a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        }
386a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    }
387e897e4d3422c8d9d8b6f051376cc2ba16e4d5945Jean Chalard                }
388199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard
389647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
3904609c02f9e61370557fee675c67263160fbf7feeJean Chalard                final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
3914609c02f9e61370557fee675c67263160fbf7feeJean Chalard                        mService.mSuggestionThreshold, mService.mLikelyThreshold, suggestionsLimit);
392199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                final WordComposer composer = new WordComposer();
393199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                final int length = text.length();
394199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                for (int i = 0; i < length; ++i) {
395199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    final int character = text.codePointAt(i);
396199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character);
397199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    final int[] proximities;
398199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    if (-1 == proximityIndex) {
399199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                        proximities = new int[] { character };
400199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    } else {
401199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                        proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
402199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                                proximityIndex,
403199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                                proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
404199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    }
405199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    composer.add(character, proximities,
406199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                            WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
4075d4c5692f11958064ba7c0de5715f30c96175400Jean Chalard                }
408199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard
409199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                final int capitalizeType = getCapitalizationType(text);
410199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                boolean isInDict = true;
411a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                DictAndProximity dictInfo = null;
412a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                try {
413a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    dictInfo = mDictionaryPool.takeOrGetNull();
414a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    if (null == dictInfo) return NOT_IN_DICT_EMPTY_SUGGESTIONS;
415a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
416a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                            dictInfo.mProximityInfo);
417a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    isInDict = dictInfo.mDictionary.isValidWord(text);
418a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    if (!isInDict && CAPITALIZE_NONE != capitalizeType) {
419a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        // We want to test the word again if it's all caps or first caps only.
420a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        // If it's fully down, we already tested it, if it's mixed case, we don't
421a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        // want to test a lowercase version of it.
422a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale));
423a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    }
424a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                } finally {
425a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    if (null != dictInfo) {
426a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        if (!mDictionaryPool.offer(dictInfo)) {
427a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                            Log.e(TAG, "Can't re-insert a dictionary into its pool");
428a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                        }
429a9876980c87748750d3edb19d72ff65bce75f024Jean Chalard                    }
430c160373b6a8e8a536ad8aa2798a33a41d3050f3bJean Chalard                }
4315bcf8ee66ceb38675a6b70fefcb574978e0fae92satok
43285782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard                final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(
43385782abaf178f6aafa1f8999123ff540f04c17bcJean Chalard                        capitalizeType, mLocale);
434199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard
435199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                if (DBG) {
436199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
437199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                            + suggestionsLimit);
438647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                    Log.i(TAG, "IsInDict = " + isInDict);
439647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                    Log.i(TAG, "LooksLikeTypo = " + (!isInDict));
440647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                    Log.i(TAG, "HasLikelySuggestions = " + result.mHasLikelySuggestions);
44151075d145a85d1acaff08c02f4d6b10b175eaa36Jean Chalard                    if (null != result.mSuggestions) {
44251075d145a85d1acaff08c02f4d6b10b175eaa36Jean Chalard                        for (String suggestion : result.mSuggestions) {
44351075d145a85d1acaff08c02f4d6b10b175eaa36Jean Chalard                            Log.i(TAG, suggestion);
44451075d145a85d1acaff08c02f4d6b10b175eaa36Jean Chalard                        }
445199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    }
446199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                }
447a562767a14c7bbac95b25e69e360fc28d6ce9e33Jean Chalard
448647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                // TODO: actually use result.mHasLikelySuggestions
449199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                final int flags =
450647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                        (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
451647db70fec321d9847f6568cc7bd2b3bd6671322Jean Chalard                                : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO);
452199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                return new SuggestionsInfo(flags, result.mSuggestions);
453199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard            } catch (RuntimeException e) {
454199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                // Don't kill the keyboard if there is a bug in the spell checker
455199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                if (DBG) {
456199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    throw e;
457199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                } else {
458199dc5e0e4236eed408650dbb0dc07d7f16bbe03Jean Chalard                    Log.e(TAG, "Exception while spellcheking: " + e);
459e897e4d3422c8d9d8b6f051376cc2ba16e4d5945Jean Chalard                    return NOT_IN_DICT_EMPTY_SUGGESTIONS;
460af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard                }
461af3b56c887b6c0a1bcbb21c50489f2d7ae65f654Jean Chalard            }
4625bcf8ee66ceb38675a6b70fefcb574978e0fae92satok        }
463022c1cc20379767966f4915e2dea65fc0b67c0d8satok    }
464022c1cc20379767966f4915e2dea65fc0b67c0d8satok}
465