Suggest.java revision a28a05e971cc242b338331a3b78276fa95188d19
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
22c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalardimport com.android.inputmethod.keyboard.Keyboard;
23043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo;
24def4551c2a570e7f575b2e9303506d790c2f335fJean Chalardimport com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
25043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard
2633e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaokaimport java.io.File;
27fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaokaimport java.util.ArrayList;
289da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalardimport java.util.Comparator;
29c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaokaimport java.util.HashSet;
30cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale;
311b06b59e28743b713947947437ea5b312477f808Jean Chalardimport java.util.concurrent.ConcurrentHashMap;
32fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaoka
33923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/**
34e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * This class loads a dictionary and provides a list of suggestions for a given sequence of
35923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * characters. This includes corrections and completions.
36923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
37a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class Suggest {
3882411d47ba7e8133ed2390c6920945e139a738cesatok    public static final String TAG = Suggest.class.getSimpleName();
39cdbbea735f590784791f0c1fe33a514c4e864836satok
40f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    // Session id for
41f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    // {@link #getSuggestedWords(WordComposer,CharSequence,ProximityInfo,boolean,int)}.
42f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    public static final int SESSION_TYPING = 0;
43f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    public static final int SESSION_GESTURE = 1;
44f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka
455475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard    // TODO: rename this to CORRECTION_OFF
46923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public static final int CORRECTION_NONE = 0;
475475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard    // TODO: rename this to CORRECTION_ON
484606de117b7541125f3f15bd6b50d77ed20e5132Jean Chalard    public static final int CORRECTION_FULL = 1;
49979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
50369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    public interface SuggestInitializationListener {
51369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka        public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
52369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    }
53369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka
548553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard    private static final boolean DBG = LatinImeLogger.sDBG;
5582411d47ba7e8133ed2390c6920945e139a738cesatok
566080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge    private Dictionary mMainDictionary;
5767fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard    private ContactsBinaryDictionary mContactsDict;
586234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard    private final ConcurrentHashMap<String, Dictionary> mDictionaries =
595f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka            CollectionUtils.newConcurrentHashMap();
60979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
610998c48ac30294b7b6b70257b390962e930b59e1Jean Chalard    public static final int MAX_SUGGESTIONS = 18;
6234386e698876c0f29b2d5b54b44db1e32b562c47Amith Yamasani
630028ed3627ff4f37a62a80f3b2c857e373cd5090satok    private float mAutoCorrectionThreshold;
64979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
658e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard    // Locale used for upper- and title-casing words
66369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    private final Locale mLocale;
678e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard
68369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    public Suggest(final Context context, final Locale locale,
69369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka            final SuggestInitializationListener listener) {
7079eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        initAsynchronously(context, locale, listener);
718e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard        mLocale = locale;
72979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
73979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
7424aee9100e92dc4c06cdb54487a4922420fa8660Jean Chalard    /* package for test */ Suggest(final Context context, final File dictionary,
7524aee9100e92dc4c06cdb54487a4922420fa8660Jean Chalard            final long startOffset, final long length, final Locale locale) {
76f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard        final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
77f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard                startOffset, length /* useFullEditDistance */, false, locale);
788e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard        mLocale = locale;
796080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = mainDict;
80d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
8133e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka    }
8233e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka
8379eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka    private void initAsynchronously(final Context context, final Locale locale,
8479eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            final SuggestInitializationListener listener) {
8579eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        resetMainDict(context, locale, listener);
863af9f05f2916e376f265974c820c369a6c63a780Jean Chalard    }
873af9f05f2916e376f265974c820c369a6c63a780Jean Chalard
881b06b59e28743b713947947437ea5b312477f808Jean Chalard    private static void addOrReplaceDictionary(
891b06b59e28743b713947947437ea5b312477f808Jean Chalard            final ConcurrentHashMap<String, Dictionary> dictionaries,
901b06b59e28743b713947947437ea5b312477f808Jean Chalard            final String key, final Dictionary dict) {
913439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        final Dictionary oldDict = (dict == null)
923439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka                ? dictionaries.remove(key)
933439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka                : dictionaries.put(key, dict);
943439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        if (oldDict != null && dict != oldDict) {
953439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka            oldDict.close();
963439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        }
973439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka    }
983439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka
9979eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka    public void resetMainDict(final Context context, final Locale locale,
10079eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            final SuggestInitializationListener listener) {
1016080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = null;
10279eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        if (listener != null) {
10379eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
104369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka        }
1053af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        new Thread("InitializeBinaryDictionary") {
106904baab25a4c6ec5d9c4bf7e562154e3f544d296satok            @Override
1073af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            public void run() {
108f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard                final DictionaryCollection newMainDict =
109f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard                        DictionaryFactory.createMainDictionaryFromManager(context, locale);
110d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard                addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
1116080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge                mMainDictionary = newMainDict;
11279eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka                if (listener != null) {
11379eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka                    listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
114369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka                }
1153af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            }
1163af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        }.start();
117cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard    }
118cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard
119c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa    // The main dictionary could have been loaded asynchronously.  Don't cache the return value
120c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa    // of this method.
121e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani    public boolean hasMainDictionary() {
1226080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        return null != mMainDictionary && mMainDictionary.isInitialized();
1236080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge    }
1246080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge
1256080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge    public Dictionary getMainDictionary() {
1266080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        return mMainDictionary;
127e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani    }
128e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani
12967fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard    public ContactsBinaryDictionary getContactsDictionary() {
13014051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        return mContactsDict;
13114051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    }
13214051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard
1331b06b59e28743b713947947437ea5b312477f808Jean Chalard    public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
1346234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard        return mDictionaries;
135bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    }
136bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
137923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
138923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
139f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard     * before the main dictionary, if set. This refers to the system-managed user dictionary.
140923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
14167fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard    public void setUserDictionary(UserBinaryDictionary userDictionary) {
142d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary);
143923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
1442bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
1452bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    /**
146699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove
147699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * the contacts dictionary by passing null to this method. In this case no contacts dictionary
148699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * won't be used.
1492bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer     */
15067fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard    public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) {
15114051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        mContactsDict = contactsDictionary;
152d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary);
1532bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
154e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa
15567fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard    public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) {
156d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
157979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
158979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
1590028ed3627ff4f37a62a80f3b2c857e373cd5090satok    public void setAutoCorrectionThreshold(float threshold) {
1601b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka        mAutoCorrectionThreshold = threshold;
161b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda    }
162b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda
16328eeb35d149468514a65379e9d0d1672cf26981eJean Chalard    public SuggestedWords getSuggestedWords(
16428eeb35d149468514a65379e9d0d1672cf26981eJean Chalard            final WordComposer wordComposer, CharSequence prevWordForBigram,
1653979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka            final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) {
166979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        LatinImeLogger.onStartSuggestion(prevWordForBigram);
167d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        if (wordComposer.isBatchMode()) {
1683979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka            return getSuggestedWordsForBatchInput(
1693979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka                    wordComposer, prevWordForBigram, proximityInfo, sessionId);
170d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        } else {
171d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
172d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                    isCorrectionEnabled);
173d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        }
174d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
175d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
176d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    // Retrieves suggestions for the typing input.
177d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    private SuggestedWords getSuggestedWordsForTypingInput(
178d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            final WordComposer wordComposer, CharSequence prevWordForBigram,
179d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
18010abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard        final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
1812d2e3480338b97b55f1a22bf2bfe89c52ba866e2Jean Chalard        final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
1822d2e3480338b97b55f1a22bf2bfe89c52ba866e2Jean Chalard                MAX_SUGGESTIONS);
1831b62ff1a3d61cd44ab88acdfcbdf0fc70a7e1b10Amith Yamasani
184c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard        final String typedWord = wordComposer.getTypedWord();
18510abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard        final String consideredWord = trailingSingleQuotesCount > 0
18610abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard                ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
187117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard                : typedWord;
188d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED);
189979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
190c677b0071d51a277413079b30f2215605637aa6bJean Chalard        final WordComposer wordComposerForLookup;
191c677b0071d51a277413079b30f2215605637aa6bJean Chalard        if (trailingSingleQuotesCount > 0) {
192c677b0071d51a277413079b30f2215605637aa6bJean Chalard            wordComposerForLookup = new WordComposer(wordComposer);
193c677b0071d51a277413079b30f2215605637aa6bJean Chalard            for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
194c677b0071d51a277413079b30f2215605637aa6bJean Chalard                wordComposerForLookup.deleteLast();
195c677b0071d51a277413079b30f2215605637aa6bJean Chalard            }
196c677b0071d51a277413079b30f2215605637aa6bJean Chalard        } else {
197c677b0071d51a277413079b30f2215605637aa6bJean Chalard            wordComposerForLookup = wordComposer;
198c677b0071d51a277413079b30f2215605637aa6bJean Chalard        }
199d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard
200d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard        for (final String key : mDictionaries.keySet()) {
201d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard            final Dictionary dictionary = mDictionaries.get(key);
202d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard            suggestionsSet.addAll(dictionary.getSuggestions(
203d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard                    wordComposerForLookup, prevWordForBigram, proximityInfo));
204923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
2059f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
2067b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        final CharSequence whitelistedWord;
2077b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        if (suggestionsSet.isEmpty()) {
2087b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = null;
2097b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
2107b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = null;
2117b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        } else {
2127b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = suggestionsSet.first().mWord;
2137b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        }
2147b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard
2158c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard        // The word can be auto-corrected if it has a whitelist entry that is not itself,
2168c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard        // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
217caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard        final boolean allowsToBeAutoCorrected = (null != whitelistedWord
218caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard                && !whitelistedWord.equals(consideredWord))
2198c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard                || (consideredWord.length() > 1 && !AutoCorrection.isInTheDictionary(mDictionaries,
2208c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard                        consideredWord, wordComposer.isFirstCharCapitalized()));
221caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard
22267af2a24157ead953607bdfd585fba3a7e6bf50cJean Chalard        final boolean hasAutoCorrection;
223966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
224966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // any attempt to do auto-correction is already shielded with a test for this flag; at the
225966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // same time, it feels wrong that the SuggestedWord object includes information about
226966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // the current settings. It may also be useful to know, when the setting is off, whether
227966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // the word *would* have been auto-corrected.
2282549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard        if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord()
229e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard                || suggestionsSet.isEmpty() || wordComposer.hasDigits()
2302549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                || wordComposer.isMostlyCaps() || wordComposer.isResumed()
2312549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                || !hasMainDictionary()) {
23290d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // If we don't have a main dictionary, we never want to auto-correct. The reason for
23390d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // this is, the user may have a contact whose name happens to match a valid word in
23490d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // their language, and it will unexpectedly auto-correct. For example, if the user
23590d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // types in English with no dictionary and has a "Will" in their contact list, "will"
23690d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no
23790d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // auto-correct.
238ea578f6b1dbcf04ffcc9c673f72a38ed2cfecdfcJean Chalard            hasAutoCorrection = false;
23994b20c90d86aa042c2f361597665045271956decJean Chalard        } else {
2401343d27de30c4010c54576d6c8bbb052c7630cbeJean Chalard            hasAutoCorrection = AutoCorrection.suggestionExceedsAutoCorrectionThreshold(
2411343d27de30c4010c54576d6c8bbb052c7630cbeJean Chalard                    suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold);
24294b20c90d86aa042c2f361597665045271956decJean Chalard        }
2439f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
244ed83d4b14366b9799bf94c3f3486dc14ebd15d0fJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsContainer =
2455f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSet);
2465110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        final int suggestionsCount = suggestionsContainer.size();
247f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard        final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
248f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard        final boolean isAllUpperCase = wordComposer.isAllUpperCase();
2495110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
2505110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard            for (int i = 0; i < suggestionsCount; ++i) {
2515110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
2525110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
2535110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                        wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
2545110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                        trailingSingleQuotesCount);
2555110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                suggestionsContainer.set(i, transformedWordInfo);
2565110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard            }
2575110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        }
2585110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard
2595110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        for (int i = 0; i < suggestionsCount; ++i) {
260ed83d4b14366b9799bf94c3f3486dc14ebd15d0fJean Chalard            final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
2615110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict);
262bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        }
263bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
264f89a75134b03bd2675c85249a184c09f83c6f80cJean Chalard        if (!TextUtils.isEmpty(typedWord)) {
265bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
26624eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
26724eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard                    Dictionary.TYPE_USER_TYPED));
26828eeb35d149468514a65379e9d0d1672cf26981eJean Chalard        }
269bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard        SuggestedWordInfo.removeDups(suggestionsContainer);
2709f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
271def4551c2a570e7f575b2e9303506d790c2f335fJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsList;
272bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard        if (DBG && !suggestionsContainer.isEmpty()) {
273bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer);
274ed9986824e1339855376771ad29fae4de921a029Jean Chalard        } else {
275bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = suggestionsContainer;
2768553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard        }
277ed9986824e1339855376771ad29fae4de921a029Jean Chalard
2784a08b2f0e4d0ee7f1d89b4eb3c77c37d987584eaJean Chalard        return new SuggestedWords(suggestionsList,
279b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // TODO: this first argument is lying. If this is a whitelisted word which is an
280b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // actual word, it says typedWordValid = false, which looks wrong. We should either
281b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // rename the attribute or change the value.
28202f1c1534c2060aaea7a9a020ce87f6c5ff5d8e0Jean Chalard                !allowsToBeAutoCorrected /* typedWordValid */,
2832549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                hasAutoCorrection, /* willAutoCorrect */
28403a35170751a635332c00bf6c272a0127a255cf6Jean Chalard                false /* isPunctuationSuggestions */,
2850142b997bf18f5d07e83b3fd403f0b3ea4736040satok                false /* isObsoleteSuggestions */,
286f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard                !wordComposer.isComposingWord() /* isPrediction */);
287923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
288923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
289d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    // Retrieves suggestions for the batch input.
290d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    private SuggestedWords getSuggestedWordsForBatchInput(
291d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            final WordComposer wordComposer, CharSequence prevWordForBigram,
2923979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka            final ProximityInfo proximityInfo, int sessionId) {
293d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
294d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                MAX_SUGGESTIONS);
295d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
296d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        // At second character typed, search the unigrams (scores being affected by bigrams)
297d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        for (final String key : mDictionaries.keySet()) {
29846fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // Skip User history dictionary for lookup
29946fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // TODO: The user history dictionary should just override getSuggestionsWithSessionId
30046fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // to make sure it doesn't return anything and we should remove this test
30146fc768e54e3d52003645494552f9e686f28f20fJean Chalard            if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
302d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                continue;
303d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            }
304d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            final Dictionary dictionary = mDictionaries.get(key);
3053979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka            suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(
3063979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka                    wordComposer, prevWordForBigram, proximityInfo, sessionId));
307d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        }
308d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
30987cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka        for (SuggestedWordInfo wordInfo : suggestionsSet) {
31087cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict);
31187cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka        }
31287cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka
313d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        final ArrayList<SuggestedWordInfo> suggestionsContainer =
3145f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSet);
315eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        final int suggestionsCount = suggestionsContainer.size();
3161eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
3171eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isAllUpperCase = wordComposer.isAllUpperCase();
318eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        if (isFirstCharCapitalized || isAllUpperCase) {
319eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            for (int i = 0; i < suggestionsCount; ++i) {
320eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
321eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
322eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                        wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
323eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                        0 /* trailingSingleQuotesCount */);
324eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                suggestionsContainer.set(i, transformedWordInfo);
325eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            }
326eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        }
327d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
328d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        SuggestedWordInfo.removeDups(suggestionsContainer);
329eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // In the batch input mode, the most relevant suggested word should act as a "typed word"
330eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
331d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        return new SuggestedWords(suggestionsContainer,
332d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                true /* typedWordValid */,
333eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                false /* willAutoCorrect */,
334d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isPunctuationSuggestions */,
335d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isObsoleteSuggestions */,
336d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isPrediction */);
337d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
338d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
3390d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
3407e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
3417e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        final SuggestedWordInfo typedWordInfo = suggestions.get(0);
3427e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        typedWordInfo.setDebugString("+");
3430d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        final int suggestionsSize = suggestions.size();
3440d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsList =
3455f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSize);
3467e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        suggestionsList.add(typedWordInfo);
3470d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // Note: i here is the index in mScores[], but the index in mSuggestions is one more
3480d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // than i because we added the typed word to mSuggestions without touching mScores.
3497e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        for (int i = 0; i < suggestionsSize - 1; ++i) {
3507e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final SuggestedWordInfo cur = suggestions.get(i + 1);
3510028ed3627ff4f37a62a80f3b2c857e373cd5090satok            final float normalizedScore = BinaryDictionary.calcNormalizedScore(
352db1939dbaa1de59eaf5693e2c89b02b323e9aac8satok                    typedWord, cur.toString(), cur.mScore);
3530d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            final String scoreInfoString;
3540d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            if (normalizedScore > 0) {
3557e518d8b8358c96b94b900f0917cdc5fd8190ce1satok                scoreInfoString = String.format("%d (%4.2f)", cur.mScore, normalizedScore);
3560d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            } else {
3577e518d8b8358c96b94b900f0917cdc5fd8190ce1satok                scoreInfoString = Integer.toString(cur.mScore);
3580d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            }
3597e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            cur.setDebugString(scoreInfoString);
3607e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            suggestionsList.add(cur);
3610d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        }
3620d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        return suggestionsList;
3630d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    }
3640d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard
365a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka    private static final class SuggestedWordInfoComparator
366a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka            implements Comparator<SuggestedWordInfo> {
3679da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        // This comparator ranks the word info with the higher frequency first. That's because
3689da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        // that's the order we want our elements in.
3699da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        @Override
3709da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
3719da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mScore > o2.mScore) return -1;
3729da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mScore < o2.mScore) return 1;
3739da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mCodePointCount < o2.mCodePointCount) return -1;
3749da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mCodePointCount > o2.mCodePointCount) return 1;
3759da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            return o1.mWord.toString().compareTo(o2.mWord.toString());
3769da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        }
3779da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard    }
3789da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard    private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
3799da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            new SuggestedWordInfoComparator();
3809da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard
381ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard    private static SuggestedWordInfo getTransformedSuggestedWordInfo(
382ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
383ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
3849011b89f4ea0d73f1ad78b2dd0a6557b950fddd9Jean Chalard        final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
385ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        if (isAllUpperCase) {
386ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            sb.append(wordInfo.mWord.toString().toUpperCase(locale));
387ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        } else if (isFirstCharCapitalized) {
388ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            sb.append(StringUtils.toTitleCase(wordInfo.mWord.toString(), locale));
389ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        } else {
390ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            sb.append(wordInfo.mWord);
391ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
392ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
393ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
394ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
39524eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard        return new SuggestedWordInfo(sb, wordInfo.mScore, wordInfo.mKind, wordInfo.mSourceDict);
396ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard    }
397ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard
39836fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani    public void close() {
3995f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka        final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
4006234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard        dictionaries.addAll(mDictionaries.values());
401c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka        for (final Dictionary dictionary : dictionaries) {
402c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka            dictionary.close();
40336fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani        }
4046080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = null;
40536fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani    }
406923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
407