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