Suggest.java revision 87d06afc66db68f0b30b36593095511314793517
1923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/*
2443c360d0afdbab091994244f045f4756feaf2b4Jean-Baptiste Queru * Copyright (C) 2008 The Android Open Source Project
3e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa *
48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License.
68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at
7e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa *
88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka *      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
118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and
148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under 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;
23043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo;
24def4551c2a570e7f575b2e9303506d790c2f335fJean Chalardimport com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
2587d06afc66db68f0b30b36593095511314793517Satoshi Kataokaimport com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
26b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasaimport com.android.inputmethod.latin.utils.AutoCorrectionUtils;
27e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.BoundedTreeSet;
28e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.CollectionUtils;
29e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.StringUtils;
30043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard
31fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaokaimport java.util.ArrayList;
329da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalardimport java.util.Comparator;
33c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaokaimport java.util.HashSet;
34cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale;
351b06b59e28743b713947947437ea5b312477f808Jean Chalardimport java.util.concurrent.ConcurrentHashMap;
36fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaoka
37923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/**
38e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * This class loads a dictionary and provides a list of suggestions for a given sequence of
39923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * characters. This includes corrections and completions.
40923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
41a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class Suggest {
4282411d47ba7e8133ed2390c6920945e139a738cesatok    public static final String TAG = Suggest.class.getSimpleName();
43cdbbea735f590784791f0c1fe33a514c4e864836satok
44f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    // Session id for
45bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
46f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    public static final int SESSION_TYPING = 0;
47f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    public static final int SESSION_GESTURE = 1;
48f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka
495475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard    // TODO: rename this to CORRECTION_OFF
50923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public static final int CORRECTION_NONE = 0;
515475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard    // TODO: rename this to CORRECTION_ON
524606de117b7541125f3f15bd6b50d77ed20e5132Jean Chalard    public static final int CORRECTION_FULL = 1;
53979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
54a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard    // Close to -2**31
55a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard    private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
56a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
57369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    public interface SuggestInitializationListener {
58369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka        public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
59369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    }
60369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka
618553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard    private static final boolean DBG = LatinImeLogger.sDBG;
6282411d47ba7e8133ed2390c6920945e139a738cesatok
636080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge    private Dictionary mMainDictionary;
6467fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard    private ContactsBinaryDictionary mContactsDict;
656234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard    private final ConcurrentHashMap<String, Dictionary> mDictionaries =
665f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka            CollectionUtils.newConcurrentHashMap();
6701a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    @UsedForTesting
6801a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    private boolean mIsCurrentlyWaitingForMainDictionary = false;
69979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
700998c48ac30294b7b6b70257b390962e930b59e1Jean Chalard    public static final int MAX_SUGGESTIONS = 18;
7134386e698876c0f29b2d5b54b44db1e32b562c47Amith Yamasani
720028ed3627ff4f37a62a80f3b2c857e373cd5090satok    private float mAutoCorrectionThreshold;
73979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
748e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard    // Locale used for upper- and title-casing words
75d91268ad9fb69b4733044b4e466e1d33f6c4725fJean Chalard    public final Locale mLocale;
768e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard
77369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    public Suggest(final Context context, final Locale locale,
78369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka            final SuggestInitializationListener listener) {
7979eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        initAsynchronously(context, locale, listener);
808e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard        mLocale = locale;
81979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
82979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
8315f6d4ae34664ea3d92827a2c3003198c0bac70bTadashi G. Takaoka    @UsedForTesting
841562fc91f015616f900b82bb44e6f1493be92c5aJean Chalard    Suggest(final AssetFileAddress[] dictionaryList, final Locale locale) {
851562fc91f015616f900b82bb44e6f1493be92c5aJean Chalard        final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
861562fc91f015616f900b82bb44e6f1493be92c5aJean Chalard                false /* useFullEditDistance */, locale);
878e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard        mLocale = locale;
886080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = mainDict;
89d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
9033e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka    }
9133e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka
9279eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka    private void initAsynchronously(final Context context, final Locale locale,
9379eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            final SuggestInitializationListener listener) {
9479eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        resetMainDict(context, locale, listener);
953af9f05f2916e376f265974c820c369a6c63a780Jean Chalard    }
963af9f05f2916e376f265974c820c369a6c63a780Jean Chalard
971b06b59e28743b713947947437ea5b312477f808Jean Chalard    private static void addOrReplaceDictionary(
981b06b59e28743b713947947437ea5b312477f808Jean Chalard            final ConcurrentHashMap<String, Dictionary> dictionaries,
991b06b59e28743b713947947437ea5b312477f808Jean Chalard            final String key, final Dictionary dict) {
1003439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        final Dictionary oldDict = (dict == null)
1013439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka                ? dictionaries.remove(key)
1023439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka                : dictionaries.put(key, dict);
1033439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        if (oldDict != null && dict != oldDict) {
1043439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka            oldDict.close();
1053439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        }
1063439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka    }
1073439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka
10879eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka    public void resetMainDict(final Context context, final Locale locale,
10979eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            final SuggestInitializationListener listener) {
11001a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard        mIsCurrentlyWaitingForMainDictionary = true;
1116080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = null;
11279eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        if (listener != null) {
11379eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
114369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka        }
1153af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        new Thread("InitializeBinaryDictionary") {
116904baab25a4c6ec5d9c4bf7e562154e3f544d296satok            @Override
1173af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            public void run() {
118f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard                final DictionaryCollection newMainDict =
119f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard                        DictionaryFactory.createMainDictionaryFromManager(context, locale);
120d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard                addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
1216080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge                mMainDictionary = newMainDict;
12279eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka                if (listener != null) {
12379eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka                    listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
124369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka                }
12501a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard                mIsCurrentlyWaitingForMainDictionary = false;
1263af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            }
1273af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        }.start();
128cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard    }
129cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard
130c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa    // The main dictionary could have been loaded asynchronously.  Don't cache the return value
131c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa    // of this method.
132e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani    public boolean hasMainDictionary() {
1336080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        return null != mMainDictionary && mMainDictionary.isInitialized();
1346080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge    }
1356080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge
13601a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    @UsedForTesting
13701a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    public boolean isCurrentlyWaitingForMainDictionary() {
13801a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard        return mIsCurrentlyWaitingForMainDictionary;
13901a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    }
14001a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard
1416080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge    public Dictionary getMainDictionary() {
1426080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        return mMainDictionary;
143e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani    }
144e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani
14567fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard    public ContactsBinaryDictionary getContactsDictionary() {
14614051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        return mContactsDict;
14714051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    }
14814051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard
1491b06b59e28743b713947947437ea5b312477f808Jean Chalard    public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
1506234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard        return mDictionaries;
151bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    }
152bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
153923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
154923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
155f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard     * before the main dictionary, if set. This refers to the system-managed user dictionary.
156923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
157bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public void setUserDictionary(final UserBinaryDictionary userDictionary) {
158d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary);
159923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
1602bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
1612bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    /**
162699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove
163699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * the contacts dictionary by passing null to this method. In this case no contacts dictionary
164699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * won't be used.
1652bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer     */
166bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
16714051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        mContactsDict = contactsDictionary;
168d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary);
1692bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
170e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa
17187d06afc66db68f0b30b36593095511314793517Satoshi Kataoka    public void setUserHistoryPredictionDictionary(
17287d06afc66db68f0b30b36593095511314793517Satoshi Kataoka            final UserHistoryPredictionDictionary userHistoryPredictionDictionary) {
17387d06afc66db68f0b30b36593095511314793517Satoshi Kataoka        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY,
17487d06afc66db68f0b30b36593095511314793517Satoshi Kataoka                userHistoryPredictionDictionary);
175979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
176979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
1770028ed3627ff4f37a62a80f3b2c857e373cd5090satok    public void setAutoCorrectionThreshold(float threshold) {
1781b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka        mAutoCorrectionThreshold = threshold;
179b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda    }
180b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda
181bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public SuggestedWords getSuggestedWords(final WordComposer wordComposer,
182bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            final String prevWordForBigram, final ProximityInfo proximityInfo,
1832dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard            final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
1842dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard            final int sessionId) {
185979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        LatinImeLogger.onStartSuggestion(prevWordForBigram);
186d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        if (wordComposer.isBatchMode()) {
1873979f060f0650cbc117eee0307d05fb0be78c6f2Satoshi Kataoka            return getSuggestedWordsForBatchInput(
1882dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard                    wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords, sessionId);
189d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        } else {
190d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            return getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
1912dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard                    blockOffensiveWords, isCorrectionEnabled);
192d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        }
193d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
194d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
195d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    // Retrieves suggestions for the typing input.
196bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    private SuggestedWords getSuggestedWordsForTypingInput(final WordComposer wordComposer,
197bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            final String prevWordForBigram, final ProximityInfo proximityInfo,
1982dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard            final boolean blockOffensiveWords, final boolean isCorrectionEnabled) {
19910abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard        final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
2002d2e3480338b97b55f1a22bf2bfe89c52ba866e2Jean Chalard        final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
2012d2e3480338b97b55f1a22bf2bfe89c52ba866e2Jean Chalard                MAX_SUGGESTIONS);
2021b62ff1a3d61cd44ab88acdfcbdf0fc70a7e1b10Amith Yamasani
203c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard        final String typedWord = wordComposer.getTypedWord();
20410abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard        final String consideredWord = trailingSingleQuotesCount > 0
20510abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard                ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
206117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard                : typedWord;
207d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED);
208979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
209c677b0071d51a277413079b30f2215605637aa6bJean Chalard        final WordComposer wordComposerForLookup;
210c677b0071d51a277413079b30f2215605637aa6bJean Chalard        if (trailingSingleQuotesCount > 0) {
211c677b0071d51a277413079b30f2215605637aa6bJean Chalard            wordComposerForLookup = new WordComposer(wordComposer);
212c677b0071d51a277413079b30f2215605637aa6bJean Chalard            for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
213c677b0071d51a277413079b30f2215605637aa6bJean Chalard                wordComposerForLookup.deleteLast();
214c677b0071d51a277413079b30f2215605637aa6bJean Chalard            }
215c677b0071d51a277413079b30f2215605637aa6bJean Chalard        } else {
216c677b0071d51a277413079b30f2215605637aa6bJean Chalard            wordComposerForLookup = wordComposer;
217c677b0071d51a277413079b30f2215605637aa6bJean Chalard        }
218d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard
219d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard        for (final String key : mDictionaries.keySet()) {
220d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard            final Dictionary dictionary = mDictionaries.get(key);
221d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard            suggestionsSet.addAll(dictionary.getSuggestions(
2222dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard                    wordComposerForLookup, prevWordForBigram, proximityInfo, blockOffensiveWords));
223923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
2249f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
225bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka        final String whitelistedWord;
2267b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        if (suggestionsSet.isEmpty()) {
2277b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = null;
2287b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
2297b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = null;
2307b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        } else {
2317b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = suggestionsSet.first().mWord;
2327b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        }
2337b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard
2348c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard        // The word can be auto-corrected if it has a whitelist entry that is not itself,
2358c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard        // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
236caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard        final boolean allowsToBeAutoCorrected = (null != whitelistedWord
237caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard                && !whitelistedWord.equals(consideredWord))
238b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasa                || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord(this,
2398c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard                        consideredWord, wordComposer.isFirstCharCapitalized()));
240caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard
24167af2a24157ead953607bdfd585fba3a7e6bf50cJean Chalard        final boolean hasAutoCorrection;
242966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
243966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // any attempt to do auto-correction is already shielded with a test for this flag; at the
244966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // same time, it feels wrong that the SuggestedWord object includes information about
245966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // the current settings. It may also be useful to know, when the setting is off, whether
246966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // the word *would* have been auto-corrected.
2472549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard        if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord()
248e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard                || suggestionsSet.isEmpty() || wordComposer.hasDigits()
2492549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                || wordComposer.isMostlyCaps() || wordComposer.isResumed()
2502549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                || !hasMainDictionary()) {
25190d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // If we don't have a main dictionary, we never want to auto-correct. The reason for
25290d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // this is, the user may have a contact whose name happens to match a valid word in
25390d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // their language, and it will unexpectedly auto-correct. For example, if the user
25490d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // types in English with no dictionary and has a "Will" in their contact list, "will"
25590d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no
25690d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // auto-correct.
257ea578f6b1dbcf04ffcc9c673f72a38ed2cfecdfcJean Chalard            hasAutoCorrection = false;
25894b20c90d86aa042c2f361597665045271956decJean Chalard        } else {
259b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasa            hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
2601343d27de30c4010c54576d6c8bbb052c7630cbeJean Chalard                    suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold);
26194b20c90d86aa042c2f361597665045271956decJean Chalard        }
2629f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
263ed83d4b14366b9799bf94c3f3486dc14ebd15d0fJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsContainer =
2645f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSet);
2655110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        final int suggestionsCount = suggestionsContainer.size();
266f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard        final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
267f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard        final boolean isAllUpperCase = wordComposer.isAllUpperCase();
2685110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
2695110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard            for (int i = 0; i < suggestionsCount; ++i) {
2705110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
2715110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
2725110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                        wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
2735110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                        trailingSingleQuotesCount);
2745110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                suggestionsContainer.set(i, transformedWordInfo);
2755110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard            }
2765110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        }
2775110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard
2785110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        for (int i = 0; i < suggestionsCount; ++i) {
279ed83d4b14366b9799bf94c3f3486dc14ebd15d0fJean Chalard            final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
2805110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict);
281bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        }
282bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
283f89a75134b03bd2675c85249a184c09f83c6f80cJean Chalard        if (!TextUtils.isEmpty(typedWord)) {
284bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
28524eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
28624eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard                    Dictionary.TYPE_USER_TYPED));
28728eeb35d149468514a65379e9d0d1672cf26981eJean Chalard        }
288bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard        SuggestedWordInfo.removeDups(suggestionsContainer);
2899f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
290def4551c2a570e7f575b2e9303506d790c2f335fJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsList;
291bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard        if (DBG && !suggestionsContainer.isEmpty()) {
292bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer);
293ed9986824e1339855376771ad29fae4de921a029Jean Chalard        } else {
294bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = suggestionsContainer;
2958553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard        }
296ed9986824e1339855376771ad29fae4de921a029Jean Chalard
2974a08b2f0e4d0ee7f1d89b4eb3c77c37d987584eaJean Chalard        return new SuggestedWords(suggestionsList,
298b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // TODO: this first argument is lying. If this is a whitelisted word which is an
299b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // actual word, it says typedWordValid = false, which looks wrong. We should either
300b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // rename the attribute or change the value.
30102f1c1534c2060aaea7a9a020ce87f6c5ff5d8e0Jean Chalard                !allowsToBeAutoCorrected /* typedWordValid */,
3022549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                hasAutoCorrection, /* willAutoCorrect */
30303a35170751a635332c00bf6c272a0127a255cf6Jean Chalard                false /* isPunctuationSuggestions */,
3040142b997bf18f5d07e83b3fd403f0b3ea4736040satok                false /* isObsoleteSuggestions */,
305f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard                !wordComposer.isComposingWord() /* isPrediction */);
306923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
307923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
308d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    // Retrieves suggestions for the batch input.
309bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    private SuggestedWords getSuggestedWordsForBatchInput(final WordComposer wordComposer,
310bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            final String prevWordForBigram, final ProximityInfo proximityInfo,
3112dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard            final boolean blockOffensiveWords, final int sessionId) {
312d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
313d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                MAX_SUGGESTIONS);
314d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
315d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        // At second character typed, search the unigrams (scores being affected by bigrams)
316d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        for (final String key : mDictionaries.keySet()) {
31746fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // Skip User history dictionary for lookup
31846fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // TODO: The user history dictionary should just override getSuggestionsWithSessionId
31946fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // to make sure it doesn't return anything and we should remove this test
32046fc768e54e3d52003645494552f9e686f28f20fJean Chalard            if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
321d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                continue;
322d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            }
323d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            final Dictionary dictionary = mDictionaries.get(key);
3242dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard            suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(wordComposer,
3252dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard                    prevWordForBigram, proximityInfo, blockOffensiveWords, sessionId));
326d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        }
327d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
32887cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka        for (SuggestedWordInfo wordInfo : suggestionsSet) {
329bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict);
33087cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka        }
33187cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka
332d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        final ArrayList<SuggestedWordInfo> suggestionsContainer =
3335f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSet);
334eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        final int suggestionsCount = suggestionsContainer.size();
3351eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
3361eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isAllUpperCase = wordComposer.isAllUpperCase();
337eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        if (isFirstCharCapitalized || isAllUpperCase) {
338eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            for (int i = 0; i < suggestionsCount; ++i) {
339eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
340eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
341eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                        wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
342eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                        0 /* trailingSingleQuotesCount */);
343eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                suggestionsContainer.set(i, transformedWordInfo);
344eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            }
345eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        }
346d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
347d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard        if (suggestionsContainer.size() > 1 && TextUtils.equals(suggestionsContainer.get(0).mWord,
348d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard                wordComposer.getRejectedBatchModeSuggestion())) {
349d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard            final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
350d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard            suggestionsContainer.add(1, rejected);
351d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard        }
352d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        SuggestedWordInfo.removeDups(suggestionsContainer);
353a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
354a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        // For some reason some suggestions with MIN_VALUE are making their way here.
355a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        // TODO: Find a more robust way to detect distractors.
356a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
357a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard            if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
358a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard                suggestionsContainer.remove(i);
359a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard            }
360a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        }
361a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
362eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // In the batch input mode, the most relevant suggested word should act as a "typed word"
363eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
364d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        return new SuggestedWords(suggestionsContainer,
365d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                true /* typedWordValid */,
366eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                false /* willAutoCorrect */,
367d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isPunctuationSuggestions */,
368d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isObsoleteSuggestions */,
369d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isPrediction */);
370d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
371d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
3720d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
3737e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
3747e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        final SuggestedWordInfo typedWordInfo = suggestions.get(0);
3757e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        typedWordInfo.setDebugString("+");
3760d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        final int suggestionsSize = suggestions.size();
3770d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsList =
3785f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSize);
3797e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        suggestionsList.add(typedWordInfo);
3800d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // Note: i here is the index in mScores[], but the index in mSuggestions is one more
3810d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // than i because we added the typed word to mSuggestions without touching mScores.
3827e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        for (int i = 0; i < suggestionsSize - 1; ++i) {
3837e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final SuggestedWordInfo cur = suggestions.get(i + 1);
3840028ed3627ff4f37a62a80f3b2c857e373cd5090satok            final float normalizedScore = BinaryDictionary.calcNormalizedScore(
385db1939dbaa1de59eaf5693e2c89b02b323e9aac8satok                    typedWord, cur.toString(), cur.mScore);
3860d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            final String scoreInfoString;
3870d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            if (normalizedScore > 0) {
38894027c7201a376107a35ec78cd21db1905662601Tadashi G. Takaoka                scoreInfoString = String.format(
38994027c7201a376107a35ec78cd21db1905662601Tadashi G. Takaoka                        Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore);
3900d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            } else {
3917e518d8b8358c96b94b900f0917cdc5fd8190ce1satok                scoreInfoString = Integer.toString(cur.mScore);
3920d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            }
3937e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            cur.setDebugString(scoreInfoString);
3947e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            suggestionsList.add(cur);
3950d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        }
3960d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        return suggestionsList;
3970d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    }
3980d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard
399a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka    private static final class SuggestedWordInfoComparator
400a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka            implements Comparator<SuggestedWordInfo> {
4019da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        // This comparator ranks the word info with the higher frequency first. That's because
4029da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        // that's the order we want our elements in.
4039da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        @Override
4049da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
4059da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mScore > o2.mScore) return -1;
4069da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mScore < o2.mScore) return 1;
4079da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mCodePointCount < o2.mCodePointCount) return -1;
4089da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mCodePointCount > o2.mCodePointCount) return 1;
409bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            return o1.mWord.compareTo(o2.mWord);
4109da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        }
4119da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard    }
4129da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard    private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
4139da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            new SuggestedWordInfoComparator();
4149da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard
415ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard    private static SuggestedWordInfo getTransformedSuggestedWordInfo(
416ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
417ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
4189011b89f4ea0d73f1ad78b2dd0a6557b950fddd9Jean Chalard        final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
419ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        if (isAllUpperCase) {
420bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            sb.append(wordInfo.mWord.toUpperCase(locale));
421ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        } else if (isFirstCharCapitalized) {
42299b93d17d53c2d587c45373831b327f7851ec0a8Jean Chalard            sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
423ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        } else {
424ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            sb.append(wordInfo.mWord);
425ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
426ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
427240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka            sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
428ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
429bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka        return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind,
430bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka                wordInfo.mSourceDict);
431ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard    }
432ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard
43336fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani    public void close() {
4345f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka        final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
4356234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard        dictionaries.addAll(mDictionaries.values());
436c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka        for (final Dictionary dictionary : dictionaries) {
437c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka            dictionary.close();
43836fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani        }
4396080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = null;
44036fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani    }
441923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
442