Suggest.java revision 15f6d4ae34664ea3d92827a2c3003198c0bac70b
1923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/* 2443c360d0afdbab091994244f045f4756feaf2b4Jean-Baptiste Queru * Copyright (C) 2008 The Android Open Source Project 3e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * 4923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * use this file except in compliance with the License. You may obtain a copy of 6923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * the License at 7e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * 8923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 9e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * 10923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 11923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * License for the specific language governing permissions and limitations under 14923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * the License. 15923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */ 16923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project 17923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectpackage com.android.inputmethod.latin; 18923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project 19923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport android.content.Context; 20923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport android.text.TextUtils; 21923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project 2215f6d4ae34664ea3d92827a2c3003198c0bac70bTadashi G. Takaokaimport com.android.inputmethod.annotations.UsedForTesting; 23c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalardimport com.android.inputmethod.keyboard.Keyboard; 24043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo; 25def4551c2a570e7f575b2e9303506d790c2f335fJean Chalardimport com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 26043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard 2733e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaokaimport java.io.File; 28fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaokaimport java.util.ArrayList; 299da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalardimport java.util.Comparator; 30c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaokaimport java.util.HashSet; 31cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale; 321b06b59e28743b713947947437ea5b312477f808Jean Chalardimport java.util.concurrent.ConcurrentHashMap; 33fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaoka 34923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/** 35e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * This class loads a dictionary and provides a list of suggestions for a given sequence of 36923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * characters. This includes corrections and completions. 37923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */ 38a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class Suggest { 3982411d47ba7e8133ed2390c6920945e139a738cesatok public static final String TAG = Suggest.class.getSimpleName(); 40cdbbea735f590784791f0c1fe33a514c4e864836satok 41f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka // Session id for 42f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka // {@link #getSuggestedWords(WordComposer,CharSequence,ProximityInfo,boolean,int)}. 43f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka public static final int SESSION_TYPING = 0; 44f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka public static final int SESSION_GESTURE = 1; 45f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka 465475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard // TODO: rename this to CORRECTION_OFF 47923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project public static final int CORRECTION_NONE = 0; 485475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard // TODO: rename this to CORRECTION_ON 494606de117b7541125f3f15bd6b50d77ed20e5132Jean Chalard public static final int CORRECTION_FULL = 1; 50979f8690967ff5409fe18f5085858ccdb8e0ccf1satok 51369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka public interface SuggestInitializationListener { 52369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); 53369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka } 54369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka 558553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard private static final boolean DBG = LatinImeLogger.sDBG; 5682411d47ba7e8133ed2390c6920945e139a738cesatok 576080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge private Dictionary mMainDictionary; 5867fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard private ContactsBinaryDictionary mContactsDict; 596234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard private final ConcurrentHashMap<String, Dictionary> mDictionaries = 605f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka CollectionUtils.newConcurrentHashMap(); 61979f8690967ff5409fe18f5085858ccdb8e0ccf1satok 620998c48ac30294b7b6b70257b390962e930b59e1Jean Chalard public static final int MAX_SUGGESTIONS = 18; 6334386e698876c0f29b2d5b54b44db1e32b562c47Amith Yamasani 640028ed3627ff4f37a62a80f3b2c857e373cd5090satok private float mAutoCorrectionThreshold; 65979f8690967ff5409fe18f5085858ccdb8e0ccf1satok 668e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard // Locale used for upper- and title-casing words 67369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka private final Locale mLocale; 688e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard 69369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka public Suggest(final Context context, final Locale locale, 70369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka final SuggestInitializationListener listener) { 7179eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka initAsynchronously(context, locale, listener); 728e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard mLocale = locale; 73979f8690967ff5409fe18f5085858ccdb8e0ccf1satok } 74979f8690967ff5409fe18f5085858ccdb8e0ccf1satok 7515f6d4ae34664ea3d92827a2c3003198c0bac70bTadashi G. Takaoka @UsedForTesting 7615f6d4ae34664ea3d92827a2c3003198c0bac70bTadashi G. Takaoka Suggest(final Context context, final File dictionary, 7724aee9100e92dc4c06cdb54487a4922420fa8660Jean Chalard final long startOffset, final long length, final Locale locale) { 78f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary, 79f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard startOffset, length /* useFullEditDistance */, false, locale); 808e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard mLocale = locale; 816080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge mMainDictionary = mainDict; 82d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict); 8333e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka } 8433e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka 8579eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka private void initAsynchronously(final Context context, final Locale locale, 8679eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka final SuggestInitializationListener listener) { 8779eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka resetMainDict(context, locale, listener); 883af9f05f2916e376f265974c820c369a6c63a780Jean Chalard } 893af9f05f2916e376f265974c820c369a6c63a780Jean Chalard 901b06b59e28743b713947947437ea5b312477f808Jean Chalard private static void addOrReplaceDictionary( 911b06b59e28743b713947947437ea5b312477f808Jean Chalard final ConcurrentHashMap<String, Dictionary> dictionaries, 921b06b59e28743b713947947437ea5b312477f808Jean Chalard final String key, final Dictionary dict) { 933439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka final Dictionary oldDict = (dict == null) 943439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka ? dictionaries.remove(key) 953439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka : dictionaries.put(key, dict); 963439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka if (oldDict != null && dict != oldDict) { 973439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka oldDict.close(); 983439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka } 993439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka } 1003439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka 10179eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka public void resetMainDict(final Context context, final Locale locale, 10279eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka final SuggestInitializationListener listener) { 1036080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge mMainDictionary = null; 10479eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka if (listener != null) { 10579eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); 106369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka } 1073af9f05f2916e376f265974c820c369a6c63a780Jean Chalard new Thread("InitializeBinaryDictionary") { 108904baab25a4c6ec5d9c4bf7e562154e3f544d296satok @Override 1093af9f05f2916e376f265974c820c369a6c63a780Jean Chalard public void run() { 110f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard final DictionaryCollection newMainDict = 111f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard DictionaryFactory.createMainDictionaryFromManager(context, locale); 112d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict); 1136080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge mMainDictionary = newMainDict; 11479eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka if (listener != null) { 11579eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka listener.onUpdateMainDictionaryAvailability(hasMainDictionary()); 116369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka } 1173af9f05f2916e376f265974c820c369a6c63a780Jean Chalard } 1183af9f05f2916e376f265974c820c369a6c63a780Jean Chalard }.start(); 119cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard } 120cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard 121c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa // The main dictionary could have been loaded asynchronously. Don't cache the return value 122c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa // of this method. 123e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani public boolean hasMainDictionary() { 1246080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge return null != mMainDictionary && mMainDictionary.isInitialized(); 1256080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge } 1266080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge 1276080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge public Dictionary getMainDictionary() { 1286080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge return mMainDictionary; 129e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani } 130e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani 13167fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard public ContactsBinaryDictionary getContactsDictionary() { 13214051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard return mContactsDict; 13314051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard } 13414051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard 1351b06b59e28743b713947947437ea5b312477f808Jean Chalard public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() { 1366234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard return mDictionaries; 137bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok } 138bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok 139923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project /** 140923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted 141f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard * before the main dictionary, if set. This refers to the system-managed user dictionary. 142923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */ 14367fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard public void setUserDictionary(UserBinaryDictionary userDictionary) { 144d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary); 145923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project } 1462bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer 1472bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer /** 148699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove 149699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard * the contacts dictionary by passing null to this method. In this case no contacts dictionary 150699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard * won't be used. 1512bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer */ 15267fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) { 15314051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard mContactsDict = contactsDictionary; 154d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary); 1552bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer } 156e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa 15767fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) { 158d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary); 159979f8690967ff5409fe18f5085858ccdb8e0ccf1satok } 160979f8690967ff5409fe18f5085858ccdb8e0ccf1satok 1610028ed3627ff4f37a62a80f3b2c857e373cd5090satok public void setAutoCorrectionThreshold(float threshold) { 1621b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka mAutoCorrectionThreshold = threshold; 163b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda } 164b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda 16528eeb35d149468514a65379e9d0d1672cf26981eJean Chalard public SuggestedWords getSuggestedWords( 16628eeb35d149468514a65379e9d0d1672cf26981eJean Chalard final WordComposer wordComposer, CharSequence prevWordForBigram, 1673979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) { 168979f8690967ff5409fe18f5085858ccdb8e0ccf1satok LatinImeLogger.onStartSuggestion(prevWordForBigram); 169d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka if (wordComposer.isBatchMode()) { 1703979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka return getSuggestedWordsForBatchInput( 1713979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka wordComposer, prevWordForBigram, proximityInfo, sessionId); 172d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka } else { 173d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo, 174d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka isCorrectionEnabled); 175d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka } 176d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka } 177d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka 178d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka // Retrieves suggestions for the typing input. 179d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka private SuggestedWords getSuggestedWordsForTypingInput( 180d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka final WordComposer wordComposer, CharSequence prevWordForBigram, 181d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) { 18210abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); 1832d2e3480338b97b55f1a22bf2bfe89c52ba866e2Jean Chalard final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, 1842d2e3480338b97b55f1a22bf2bfe89c52ba866e2Jean Chalard MAX_SUGGESTIONS); 1851b62ff1a3d61cd44ab88acdfcbdf0fc70a7e1b10Amith Yamasani 186c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard final String typedWord = wordComposer.getTypedWord(); 18710abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard final String consideredWord = trailingSingleQuotesCount > 0 18810abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount) 189117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard : typedWord; 190d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED); 191979f8690967ff5409fe18f5085858ccdb8e0ccf1satok 192c677b0071d51a277413079b30f2215605637aa6bJean Chalard final WordComposer wordComposerForLookup; 193c677b0071d51a277413079b30f2215605637aa6bJean Chalard if (trailingSingleQuotesCount > 0) { 194c677b0071d51a277413079b30f2215605637aa6bJean Chalard wordComposerForLookup = new WordComposer(wordComposer); 195c677b0071d51a277413079b30f2215605637aa6bJean Chalard for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) { 196c677b0071d51a277413079b30f2215605637aa6bJean Chalard wordComposerForLookup.deleteLast(); 197c677b0071d51a277413079b30f2215605637aa6bJean Chalard } 198c677b0071d51a277413079b30f2215605637aa6bJean Chalard } else { 199c677b0071d51a277413079b30f2215605637aa6bJean Chalard wordComposerForLookup = wordComposer; 200c677b0071d51a277413079b30f2215605637aa6bJean Chalard } 201d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard 202d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard for (final String key : mDictionaries.keySet()) { 203d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard final Dictionary dictionary = mDictionaries.get(key); 204d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard suggestionsSet.addAll(dictionary.getSuggestions( 205d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard wordComposerForLookup, prevWordForBigram, proximityInfo)); 206923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project } 2079f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok 2087b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard final CharSequence whitelistedWord; 2097b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard if (suggestionsSet.isEmpty()) { 2107b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard whitelistedWord = null; 2117b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) { 2127b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard whitelistedWord = null; 2137b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard } else { 2147b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard whitelistedWord = suggestionsSet.first().mWord; 2157b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard } 2167b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard 2178c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard // The word can be auto-corrected if it has a whitelist entry that is not itself, 2188c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard // or if it's a 2+ characters non-word (i.e. it's not in the dictionary). 219caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard final boolean allowsToBeAutoCorrected = (null != whitelistedWord 220caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard && !whitelistedWord.equals(consideredWord)) 2218c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard || (consideredWord.length() > 1 && !AutoCorrection.isInTheDictionary(mDictionaries, 2228c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard consideredWord, wordComposer.isFirstCharCapitalized())); 223caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard 22467af2a24157ead953607bdfd585fba3a7e6bf50cJean Chalard final boolean hasAutoCorrection; 225966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because 226966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard // any attempt to do auto-correction is already shielded with a test for this flag; at the 227966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard // same time, it feels wrong that the SuggestedWord object includes information about 228966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard // the current settings. It may also be useful to know, when the setting is off, whether 229966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard // the word *would* have been auto-corrected. 2302549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord() 231e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard || suggestionsSet.isEmpty() || wordComposer.hasDigits() 2322549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard || wordComposer.isMostlyCaps() || wordComposer.isResumed() 2332549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard || !hasMainDictionary()) { 23490d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard // If we don't have a main dictionary, we never want to auto-correct. The reason for 23590d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard // this is, the user may have a contact whose name happens to match a valid word in 23690d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard // their language, and it will unexpectedly auto-correct. For example, if the user 23790d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard // types in English with no dictionary and has a "Will" in their contact list, "will" 23890d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no 23990d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard // auto-correct. 240ea578f6b1dbcf04ffcc9c673f72a38ed2cfecdfcJean Chalard hasAutoCorrection = false; 24194b20c90d86aa042c2f361597665045271956decJean Chalard } else { 2421343d27de30c4010c54576d6c8bbb052c7630cbeJean Chalard hasAutoCorrection = AutoCorrection.suggestionExceedsAutoCorrectionThreshold( 2431343d27de30c4010c54576d6c8bbb052c7630cbeJean Chalard suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold); 24494b20c90d86aa042c2f361597665045271956decJean Chalard } 2459f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok 246ed83d4b14366b9799bf94c3f3486dc14ebd15d0fJean Chalard final ArrayList<SuggestedWordInfo> suggestionsContainer = 2475f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka CollectionUtils.newArrayList(suggestionsSet); 2485110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard final int suggestionsCount = suggestionsContainer.size(); 249f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); 250f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard final boolean isAllUpperCase = wordComposer.isAllUpperCase(); 2515110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { 2525110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard for (int i = 0; i < suggestionsCount; ++i) { 2535110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); 2545110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( 2555110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, 2565110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard trailingSingleQuotesCount); 2575110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard suggestionsContainer.set(i, transformedWordInfo); 2585110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard } 2595110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard } 2605110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard 2615110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard for (int i = 0; i < suggestionsCount; ++i) { 262ed83d4b14366b9799bf94c3f3486dc14ebd15d0fJean Chalard final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); 2635110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict); 264bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok } 265bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok 266f89a75134b03bd2675c85249a184c09f83c6f80cJean Chalard if (!TextUtils.isEmpty(typedWord)) { 267bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard suggestionsContainer.add(0, new SuggestedWordInfo(typedWord, 26824eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED, 26924eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard Dictionary.TYPE_USER_TYPED)); 27028eeb35d149468514a65379e9d0d1672cf26981eJean Chalard } 271bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard SuggestedWordInfo.removeDups(suggestionsContainer); 2729f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok 273def4551c2a570e7f575b2e9303506d790c2f335fJean Chalard final ArrayList<SuggestedWordInfo> suggestionsList; 274bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard if (DBG && !suggestionsContainer.isEmpty()) { 275bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer); 276ed9986824e1339855376771ad29fae4de921a029Jean Chalard } else { 277bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard suggestionsList = suggestionsContainer; 2788553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard } 279ed9986824e1339855376771ad29fae4de921a029Jean Chalard 2804a08b2f0e4d0ee7f1d89b4eb3c77c37d987584eaJean Chalard return new SuggestedWords(suggestionsList, 281b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard // TODO: this first argument is lying. If this is a whitelisted word which is an 282b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard // actual word, it says typedWordValid = false, which looks wrong. We should either 283b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard // rename the attribute or change the value. 28402f1c1534c2060aaea7a9a020ce87f6c5ff5d8e0Jean Chalard !allowsToBeAutoCorrected /* typedWordValid */, 2852549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard hasAutoCorrection, /* willAutoCorrect */ 28603a35170751a635332c00bf6c272a0127a255cf6Jean Chalard false /* isPunctuationSuggestions */, 2870142b997bf18f5d07e83b3fd403f0b3ea4736040satok false /* isObsoleteSuggestions */, 288f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard !wordComposer.isComposingWord() /* isPrediction */); 289923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project } 290923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project 291d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka // Retrieves suggestions for the batch input. 292d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka private SuggestedWords getSuggestedWordsForBatchInput( 293d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka final WordComposer wordComposer, CharSequence prevWordForBigram, 2943979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka final ProximityInfo proximityInfo, int sessionId) { 295d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, 296d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka MAX_SUGGESTIONS); 297d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka 298d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka // At second character typed, search the unigrams (scores being affected by bigrams) 299d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka for (final String key : mDictionaries.keySet()) { 30046fc768e54e3d52003645494552f9e686f28f20fJean Chalard // Skip User history dictionary for lookup 30146fc768e54e3d52003645494552f9e686f28f20fJean Chalard // TODO: The user history dictionary should just override getSuggestionsWithSessionId 30246fc768e54e3d52003645494552f9e686f28f20fJean Chalard // to make sure it doesn't return anything and we should remove this test 30346fc768e54e3d52003645494552f9e686f28f20fJean Chalard if (key.equals(Dictionary.TYPE_USER_HISTORY)) { 304d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka continue; 305d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka } 306d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka final Dictionary dictionary = mDictionaries.get(key); 3073979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId( 3083979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka wordComposer, prevWordForBigram, proximityInfo, sessionId)); 309d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka } 310d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka 31187cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka for (SuggestedWordInfo wordInfo : suggestionsSet) { 31287cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict); 31387cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka } 31487cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka 315d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka final ArrayList<SuggestedWordInfo> suggestionsContainer = 3165f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka CollectionUtils.newArrayList(suggestionsSet); 317eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang final int suggestionsCount = suggestionsContainer.size(); 3181eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock(); 3191eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard final boolean isAllUpperCase = wordComposer.isAllUpperCase(); 320eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang if (isFirstCharCapitalized || isAllUpperCase) { 321eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang for (int i = 0; i < suggestionsCount; ++i) { 322eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); 323eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( 324eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized, 325eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang 0 /* trailingSingleQuotesCount */); 326eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang suggestionsContainer.set(i, transformedWordInfo); 327eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang } 328eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang } 329d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka 330d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka SuggestedWordInfo.removeDups(suggestionsContainer); 331eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang // In the batch input mode, the most relevant suggested word should act as a "typed word" 332eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false). 333d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka return new SuggestedWords(suggestionsContainer, 334d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka true /* typedWordValid */, 335eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang false /* willAutoCorrect */, 336d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka false /* isPunctuationSuggestions */, 337d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka false /* isObsoleteSuggestions */, 338d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka false /* isPrediction */); 339d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka } 340d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka 3410d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo( 3427e518d8b8358c96b94b900f0917cdc5fd8190ce1satok final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) { 3437e518d8b8358c96b94b900f0917cdc5fd8190ce1satok final SuggestedWordInfo typedWordInfo = suggestions.get(0); 3447e518d8b8358c96b94b900f0917cdc5fd8190ce1satok typedWordInfo.setDebugString("+"); 3450d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard final int suggestionsSize = suggestions.size(); 3460d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard final ArrayList<SuggestedWordInfo> suggestionsList = 3475f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka CollectionUtils.newArrayList(suggestionsSize); 3487e518d8b8358c96b94b900f0917cdc5fd8190ce1satok suggestionsList.add(typedWordInfo); 3490d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard // Note: i here is the index in mScores[], but the index in mSuggestions is one more 3500d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard // than i because we added the typed word to mSuggestions without touching mScores. 3517e518d8b8358c96b94b900f0917cdc5fd8190ce1satok for (int i = 0; i < suggestionsSize - 1; ++i) { 3527e518d8b8358c96b94b900f0917cdc5fd8190ce1satok final SuggestedWordInfo cur = suggestions.get(i + 1); 3530028ed3627ff4f37a62a80f3b2c857e373cd5090satok final float normalizedScore = BinaryDictionary.calcNormalizedScore( 354db1939dbaa1de59eaf5693e2c89b02b323e9aac8satok typedWord, cur.toString(), cur.mScore); 3550d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard final String scoreInfoString; 3560d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard if (normalizedScore > 0) { 3577e518d8b8358c96b94b900f0917cdc5fd8190ce1satok scoreInfoString = String.format("%d (%4.2f)", cur.mScore, normalizedScore); 3580d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard } else { 3597e518d8b8358c96b94b900f0917cdc5fd8190ce1satok scoreInfoString = Integer.toString(cur.mScore); 3600d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard } 3617e518d8b8358c96b94b900f0917cdc5fd8190ce1satok cur.setDebugString(scoreInfoString); 3627e518d8b8358c96b94b900f0917cdc5fd8190ce1satok suggestionsList.add(cur); 3630d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard } 3640d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard return suggestionsList; 3650d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard } 3660d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard 367a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka private static final class SuggestedWordInfoComparator 368a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka implements Comparator<SuggestedWordInfo> { 3699da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard // This comparator ranks the word info with the higher frequency first. That's because 3709da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard // that's the order we want our elements in. 3719da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard @Override 3729da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) { 3739da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard if (o1.mScore > o2.mScore) return -1; 3749da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard if (o1.mScore < o2.mScore) return 1; 3759da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard if (o1.mCodePointCount < o2.mCodePointCount) return -1; 3769da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard if (o1.mCodePointCount > o2.mCodePointCount) return 1; 3779da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard return o1.mWord.toString().compareTo(o2.mWord.toString()); 3789da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard } 3799da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard } 3809da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = 3819da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard new SuggestedWordInfoComparator(); 3829da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard 383ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard private static SuggestedWordInfo getTransformedSuggestedWordInfo( 384ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase, 385ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) { 3869011b89f4ea0d73f1ad78b2dd0a6557b950fddd9Jean Chalard final StringBuilder sb = new StringBuilder(wordInfo.mWord.length()); 387ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard if (isAllUpperCase) { 388ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard sb.append(wordInfo.mWord.toString().toUpperCase(locale)); 389ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard } else if (isFirstCharCapitalized) { 390ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard sb.append(StringUtils.toTitleCase(wordInfo.mWord.toString(), locale)); 391ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard } else { 392ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard sb.append(wordInfo.mWord); 393ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard } 394ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) { 395ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE); 396ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard } 39724eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard return new SuggestedWordInfo(sb, wordInfo.mScore, wordInfo.mKind, wordInfo.mSourceDict); 398ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard } 399ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard 40036fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani public void close() { 4015f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet(); 4026234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard dictionaries.addAll(mDictionaries.values()); 403c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka for (final Dictionary dictionary : dictionaries) { 404c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka dictionary.close(); 40536fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani } 4066080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge mMainDictionary = null; 40736fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani } 408923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project} 409