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