Suggest.java revision 9666a228153bb2269da8983762bdd47e448f2cec
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;
20d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataokaimport android.preference.PreferenceManager;
21923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport android.text.TextUtils;
22d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataokaimport android.util.Log;
23923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
2415f6d4ae34664ea3d92827a2c3003198c0bac70bTadashi G. Takaokaimport com.android.inputmethod.annotations.UsedForTesting;
25043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo;
26def4551c2a570e7f575b2e9303506d790c2f335fJean Chalardimport com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
27366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataokaimport com.android.inputmethod.latin.personalization.PersonalizationDictionary;
2860586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataokaimport com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
2987d06afc66db68f0b30b36593095511314793517Satoshi Kataokaimport com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
30d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataokaimport com.android.inputmethod.latin.settings.Settings;
31b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasaimport com.android.inputmethod.latin.utils.AutoCorrectionUtils;
32e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.BoundedTreeSet;
33e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.CollectionUtils;
34e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.StringUtils;
35043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard
36fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaokaimport java.util.ArrayList;
379da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalardimport java.util.Comparator;
38c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaokaimport java.util.HashSet;
39cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale;
401b06b59e28743b713947947437ea5b312477f808Jean Chalardimport java.util.concurrent.ConcurrentHashMap;
41fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaoka
42923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/**
43e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * This class loads a dictionary and provides a list of suggestions for a given sequence of
44923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * characters. This includes corrections and completions.
45923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
46a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class Suggest {
4782411d47ba7e8133ed2390c6920945e139a738cesatok    public static final String TAG = Suggest.class.getSimpleName();
48cdbbea735f590784791f0c1fe33a514c4e864836satok
49f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    // Session id for
50bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
51f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    public static final int SESSION_TYPING = 0;
52f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    public static final int SESSION_GESTURE = 1;
53f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka
545475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard    // TODO: rename this to CORRECTION_OFF
55923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public static final int CORRECTION_NONE = 0;
565475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard    // TODO: rename this to CORRECTION_ON
574606de117b7541125f3f15bd6b50d77ed20e5132Jean Chalard    public static final int CORRECTION_FULL = 1;
58979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
59a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard    // Close to -2**31
60a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard    private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
61a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
62d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka    public static final int MAX_SUGGESTIONS = 18;
63d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka
64369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    public interface SuggestInitializationListener {
65369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka        public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
66369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    }
67369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka
688553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard    private static final boolean DBG = LatinImeLogger.sDBG;
6982411d47ba7e8133ed2390c6920945e139a738cesatok
706234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard    private final ConcurrentHashMap<String, Dictionary> mDictionaries =
715f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka            CollectionUtils.newConcurrentHashMap();
72d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka    private HashSet<String> mOnlyDictionarySetForDebug = null;
73d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka    private Dictionary mMainDictionary;
74d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka    private ContactsBinaryDictionary mContactsDict;
7501a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    @UsedForTesting
7601a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    private boolean mIsCurrentlyWaitingForMainDictionary = false;
77979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
780028ed3627ff4f37a62a80f3b2c857e373cd5090satok    private float mAutoCorrectionThreshold;
79979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
808e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard    // Locale used for upper- and title-casing words
81d91268ad9fb69b4733044b4e466e1d33f6c4725fJean Chalard    public final Locale mLocale;
828e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard
83369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka    public Suggest(final Context context, final Locale locale,
84369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka            final SuggestInitializationListener listener) {
8579eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        initAsynchronously(context, locale, listener);
868e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard        mLocale = locale;
87d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        // initialize a debug flag for the personalization
88d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        if (Settings.readUseOnlyPersonalizationDictionaryForDebug(
89d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka                PreferenceManager.getDefaultSharedPreferences(context))) {
90d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka            mOnlyDictionarySetForDebug = new HashSet<String>();
91d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka            mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
92d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka            mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
93d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        }
94979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
95979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
9615f6d4ae34664ea3d92827a2c3003198c0bac70bTadashi G. Takaoka    @UsedForTesting
971562fc91f015616f900b82bb44e6f1493be92c5aJean Chalard    Suggest(final AssetFileAddress[] dictionaryList, final Locale locale) {
981562fc91f015616f900b82bb44e6f1493be92c5aJean Chalard        final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
991562fc91f015616f900b82bb44e6f1493be92c5aJean Chalard                false /* useFullEditDistance */, locale);
1008e17f6d93a3b079eab41450539b9890763fb6e3fJean Chalard        mLocale = locale;
1016080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = mainDict;
102d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, mainDict);
10333e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka    }
10433e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka
10579eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka    private void initAsynchronously(final Context context, final Locale locale,
10679eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            final SuggestInitializationListener listener) {
10779eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        resetMainDict(context, locale, listener);
1083af9f05f2916e376f265974c820c369a6c63a780Jean Chalard    }
1093af9f05f2916e376f265974c820c369a6c63a780Jean Chalard
110d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka    private void addOrReplaceDictionaryInternal(final String key, final Dictionary dict) {
111d09ad2e3683a25a4fb461dfcf3e6f4d92a5822ddSatoshi Kataoka        if (mOnlyDictionarySetForDebug != null && !mOnlyDictionarySetForDebug.contains(key)) {
112d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka            Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
113d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka            return;
114d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        }
115d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        addOrReplaceDictionary(mDictionaries, key, dict);
116d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka    }
117d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka
1181b06b59e28743b713947947437ea5b312477f808Jean Chalard    private static void addOrReplaceDictionary(
1191b06b59e28743b713947947437ea5b312477f808Jean Chalard            final ConcurrentHashMap<String, Dictionary> dictionaries,
1201b06b59e28743b713947947437ea5b312477f808Jean Chalard            final String key, final Dictionary dict) {
1213439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        final Dictionary oldDict = (dict == null)
1223439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka                ? dictionaries.remove(key)
1233439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka                : dictionaries.put(key, dict);
1243439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        if (oldDict != null && dict != oldDict) {
1253439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka            oldDict.close();
1263439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        }
1273439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka    }
1283439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka
12979eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka    public void resetMainDict(final Context context, final Locale locale,
13079eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            final SuggestInitializationListener listener) {
13101a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard        mIsCurrentlyWaitingForMainDictionary = true;
1326080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = null;
13379eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka        if (listener != null) {
13479eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka            listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
135369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka        }
1363af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        new Thread("InitializeBinaryDictionary") {
137904baab25a4c6ec5d9c4bf7e562154e3f544d296satok            @Override
1383af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            public void run() {
139f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard                final DictionaryCollection newMainDict =
140f0e12a969974987f1b97929886c6ebe6a685c538Jean Chalard                        DictionaryFactory.createMainDictionaryFromManager(context, locale);
141d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka                addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, newMainDict);
1426080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge                mMainDictionary = newMainDict;
14379eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka                if (listener != null) {
14479eefda0d3ab5e03c2d012ed8ea1898004781871Tadashi G. Takaoka                    listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
145369e54cc338d8115d63138663b882f56208d7ec3Tadashi G. Takaoka                }
14601a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard                mIsCurrentlyWaitingForMainDictionary = false;
1473af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            }
1483af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        }.start();
149cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard    }
150cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard
151c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa    // The main dictionary could have been loaded asynchronously.  Don't cache the return value
152c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa    // of this method.
153e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani    public boolean hasMainDictionary() {
1546080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        return null != mMainDictionary && mMainDictionary.isInitialized();
1556080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge    }
1566080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge
15701a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    @UsedForTesting
15801a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    public boolean isCurrentlyWaitingForMainDictionary() {
15901a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard        return mIsCurrentlyWaitingForMainDictionary;
16001a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard    }
16101a4ebcd88f8a7001aac2f7f45293ceab717a30dJean Chalard
1626080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge    public Dictionary getMainDictionary() {
1636080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        return mMainDictionary;
164e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani    }
165e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani
16667fd0c240d7c37b06e05333347fd17acf59fadf8Jean Chalard    public ContactsBinaryDictionary getContactsDictionary() {
16714051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        return mContactsDict;
16814051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    }
16914051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard
1701b06b59e28743b713947947437ea5b312477f808Jean Chalard    public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
1716234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard        return mDictionaries;
172bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    }
173bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
174923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
175923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
176f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard     * before the main dictionary, if set. This refers to the system-managed user dictionary.
177923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
178bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public void setUserDictionary(final UserBinaryDictionary userDictionary) {
179d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary);
180923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
1812bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
1822bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    /**
183699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove
184699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * the contacts dictionary by passing null to this method. In this case no contacts dictionary
185699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * won't be used.
1862bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer     */
187bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
18814051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        mContactsDict = contactsDictionary;
189d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
1902bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
191e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa
19287d06afc66db68f0b30b36593095511314793517Satoshi Kataoka    public void setUserHistoryPredictionDictionary(
19387d06afc66db68f0b30b36593095511314793517Satoshi Kataoka            final UserHistoryPredictionDictionary userHistoryPredictionDictionary) {
194d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY,
19587d06afc66db68f0b30b36593095511314793517Satoshi Kataoka                userHistoryPredictionDictionary);
196979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
197979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
19860586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka    public void setPersonalizationPredictionDictionary(
19960586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka            final PersonalizationPredictionDictionary personalizationPredictionDictionary) {
200d7491e6e817e8954d15aa91300d209fb7e8bf384Satoshi Kataoka        addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
20160586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka                personalizationPredictionDictionary);
20260586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka    }
20360586b57cf4ca4af16cf9a9261aaba9490f128bcSatoshi Kataoka
204366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka    public void setPersonalizationDictionary(
205366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka            final PersonalizationDictionary personalizationDictionary) {
206366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka        addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION,
207366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka                personalizationDictionary);
208366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka    }
209366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka
2100028ed3627ff4f37a62a80f3b2c857e373cd5090satok    public void setAutoCorrectionThreshold(float threshold) {
2111b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka        mAutoCorrectionThreshold = threshold;
212b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda    }
213b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda
2149666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    public interface OnGetSuggestedWordsCallback {
2159666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada        public void onGetSuggestedWords(final SuggestedWords suggestedWords);
2169666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    }
2179666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada
2189666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    public void getSuggestedWords(final WordComposer wordComposer,
219bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            final String prevWordForBigram, final ProximityInfo proximityInfo,
2202dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard            final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
2219666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada            final int[] additionalFeaturesOptions, final int sessionId,
2229666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada            final OnGetSuggestedWordsCallback callback) {
223979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        LatinImeLogger.onStartSuggestion(prevWordForBigram);
224d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        if (wordComposer.isBatchMode()) {
2259666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada            getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo,
2269666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada                    blockOffensiveWords, additionalFeaturesOptions, sessionId, callback);
227d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        } else {
2289666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada            getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
2299666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada                    blockOffensiveWords, isCorrectionEnabled, additionalFeaturesOptions, callback);
230d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        }
231d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
232d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
2339666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // Retrieves suggestions for the typing input
2349666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // and calls the callback function with the suggestions.
2359666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    private void getSuggestedWordsForTypingInput(final WordComposer wordComposer,
236bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            final String prevWordForBigram, final ProximityInfo proximityInfo,
237fe87f5f41744a633a2ed91af9b171bda2637649eKeisuke Kuroyanagi            final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
2389666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada            final int[] additionalFeaturesOptions, final OnGetSuggestedWordsCallback callback) {
23910abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard        final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
2402d2e3480338b97b55f1a22bf2bfe89c52ba866e2Jean Chalard        final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
2412d2e3480338b97b55f1a22bf2bfe89c52ba866e2Jean Chalard                MAX_SUGGESTIONS);
2421b62ff1a3d61cd44ab88acdfcbdf0fc70a7e1b10Amith Yamasani
243c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard        final String typedWord = wordComposer.getTypedWord();
24410abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard        final String consideredWord = trailingSingleQuotesCount > 0
24510abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard                ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
246117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard                : typedWord;
247d8f0caa406a0ca1df488baeb3af05528085755b7Jean Chalard        LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED);
248979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
249c677b0071d51a277413079b30f2215605637aa6bJean Chalard        final WordComposer wordComposerForLookup;
250c677b0071d51a277413079b30f2215605637aa6bJean Chalard        if (trailingSingleQuotesCount > 0) {
251c677b0071d51a277413079b30f2215605637aa6bJean Chalard            wordComposerForLookup = new WordComposer(wordComposer);
252c677b0071d51a277413079b30f2215605637aa6bJean Chalard            for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
253c677b0071d51a277413079b30f2215605637aa6bJean Chalard                wordComposerForLookup.deleteLast();
254c677b0071d51a277413079b30f2215605637aa6bJean Chalard            }
255c677b0071d51a277413079b30f2215605637aa6bJean Chalard        } else {
256c677b0071d51a277413079b30f2215605637aa6bJean Chalard            wordComposerForLookup = wordComposer;
257c677b0071d51a277413079b30f2215605637aa6bJean Chalard        }
258d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard
259d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard        for (final String key : mDictionaries.keySet()) {
260d8afa2fbe13adf9f512fd294056a884a0edb0573Jean Chalard            final Dictionary dictionary = mDictionaries.get(key);
2619666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada            suggestionsSet.addAll(dictionary.getSuggestions(wordComposerForLookup,
2629666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada                    prevWordForBigram, proximityInfo, blockOffensiveWords,
263fe87f5f41744a633a2ed91af9b171bda2637649eKeisuke Kuroyanagi                    additionalFeaturesOptions));
264923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
2659f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
266bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka        final String whitelistedWord;
2677b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        if (suggestionsSet.isEmpty()) {
2687b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = null;
2697b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
2707b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = null;
2717b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        } else {
2727b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard            whitelistedWord = suggestionsSet.first().mWord;
2737b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard        }
2747b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard
2758c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard        // The word can be auto-corrected if it has a whitelist entry that is not itself,
2768c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard        // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
277caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard        final boolean allowsToBeAutoCorrected = (null != whitelistedWord
278caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard                && !whitelistedWord.equals(consideredWord))
279b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasa                || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord(this,
2808c06a468e0bffa5cedc7d782be4754da70d9a657Jean Chalard                        consideredWord, wordComposer.isFirstCharCapitalized()));
281caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard
28267af2a24157ead953607bdfd585fba3a7e6bf50cJean Chalard        final boolean hasAutoCorrection;
283966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
284966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // any attempt to do auto-correction is already shielded with a test for this flag; at the
285966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // same time, it feels wrong that the SuggestedWord object includes information about
286966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // the current settings. It may also be useful to know, when the setting is off, whether
287966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // the word *would* have been auto-corrected.
2882549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard        if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord()
289e7c471a52f38c48cd38e412d88901bddb6f903a9Jean Chalard                || suggestionsSet.isEmpty() || wordComposer.hasDigits()
2902549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                || wordComposer.isMostlyCaps() || wordComposer.isResumed()
2912549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                || !hasMainDictionary()) {
29290d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // If we don't have a main dictionary, we never want to auto-correct. The reason for
29390d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // this is, the user may have a contact whose name happens to match a valid word in
29490d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // their language, and it will unexpectedly auto-correct. For example, if the user
29590d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // types in English with no dictionary and has a "Will" in their contact list, "will"
29690d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no
29790d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // auto-correct.
298ea578f6b1dbcf04ffcc9c673f72a38ed2cfecdfcJean Chalard            hasAutoCorrection = false;
29994b20c90d86aa042c2f361597665045271956decJean Chalard        } else {
300b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasa            hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
3011343d27de30c4010c54576d6c8bbb052c7630cbeJean Chalard                    suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold);
30294b20c90d86aa042c2f361597665045271956decJean Chalard        }
3039f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
304ed83d4b14366b9799bf94c3f3486dc14ebd15d0fJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsContainer =
3055f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSet);
3065110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        final int suggestionsCount = suggestionsContainer.size();
307f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard        final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
308f5b55cb70c9d6012e1aa2b201c4785530afab168Jean Chalard        final boolean isAllUpperCase = wordComposer.isAllUpperCase();
3095110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
3105110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard            for (int i = 0; i < suggestionsCount; ++i) {
3115110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
3125110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
3135110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                        wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
3145110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                        trailingSingleQuotesCount);
3155110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard                suggestionsContainer.set(i, transformedWordInfo);
3165110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard            }
3175110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        }
3185110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard
3195110e2cb5115bc7d8337a63427b895eeb74c9cd5Jean Chalard        for (int i = 0; i < suggestionsCount; ++i) {
320ed83d4b14366b9799bf94c3f3486dc14ebd15d0fJean Chalard            final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
321e8ef09567077211da034a77b457fd5f87e70f6f0Jean Chalard            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(),
322e8ef09567077211da034a77b457fd5f87e70f6f0Jean Chalard                    wordInfo.mSourceDict.mDictType);
323bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        }
324bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
325f89a75134b03bd2675c85249a184c09f83c6f80cJean Chalard        if (!TextUtils.isEmpty(typedWord)) {
326bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
32724eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
328e8ef09567077211da034a77b457fd5f87e70f6f0Jean Chalard                    Dictionary.DICTIONARY_USER_TYPED,
329ef1e363016623ccf409c8c270f2c1e35a67734c9Jean Chalard                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */));
33028eeb35d149468514a65379e9d0d1672cf26981eJean Chalard        }
331bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard        SuggestedWordInfo.removeDups(suggestionsContainer);
3329f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
333def4551c2a570e7f575b2e9303506d790c2f335fJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsList;
334bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard        if (DBG && !suggestionsContainer.isEmpty()) {
335bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer);
336ed9986824e1339855376771ad29fae4de921a029Jean Chalard        } else {
337bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = suggestionsContainer;
3388553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard        }
339ed9986824e1339855376771ad29fae4de921a029Jean Chalard
3409666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada        callback.onGetSuggestedWords(new SuggestedWords(suggestionsList,
341b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // TODO: this first argument is lying. If this is a whitelisted word which is an
342b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // actual word, it says typedWordValid = false, which looks wrong. We should either
343b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // rename the attribute or change the value.
34402f1c1534c2060aaea7a9a020ce87f6c5ff5d8e0Jean Chalard                !allowsToBeAutoCorrected /* typedWordValid */,
3452549b4978e5b0460d0f34a5e4016374ac2198753Jean Chalard                hasAutoCorrection, /* willAutoCorrect */
34603a35170751a635332c00bf6c272a0127a255cf6Jean Chalard                false /* isPunctuationSuggestions */,
3470142b997bf18f5d07e83b3fd403f0b3ea4736040satok                false /* isObsoleteSuggestions */,
3489666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada                !wordComposer.isComposingWord() /* isPrediction */));
349923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
350923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
3519666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // Retrieves suggestions for the batch input
3529666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // and calls the callback function with the suggestions.
3539666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
354bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            final String prevWordForBigram, final ProximityInfo proximityInfo,
355fe87f5f41744a633a2ed91af9b171bda2637649eKeisuke Kuroyanagi            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
3569666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada            final int sessionId, final OnGetSuggestedWordsCallback callback) {
357d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator,
358d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                MAX_SUGGESTIONS);
359d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
360d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        // At second character typed, search the unigrams (scores being affected by bigrams)
361d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        for (final String key : mDictionaries.keySet()) {
36246fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // Skip User history dictionary for lookup
36346fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // TODO: The user history dictionary should just override getSuggestionsWithSessionId
36446fc768e54e3d52003645494552f9e686f28f20fJean Chalard            // to make sure it doesn't return anything and we should remove this test
36546fc768e54e3d52003645494552f9e686f28f20fJean Chalard            if (key.equals(Dictionary.TYPE_USER_HISTORY)) {
366d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                continue;
367d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            }
368d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka            final Dictionary dictionary = mDictionaries.get(key);
3692dbb5957e3c8354fa9bcb1e08c7ce81387b7fe25Jean Chalard            suggestionsSet.addAll(dictionary.getSuggestionsWithSessionId(wordComposer,
370fe87f5f41744a633a2ed91af9b171bda2637649eKeisuke Kuroyanagi                    prevWordForBigram, proximityInfo, blockOffensiveWords,
371fe87f5f41744a633a2ed91af9b171bda2637649eKeisuke Kuroyanagi                    additionalFeaturesOptions, sessionId));
372d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        }
373d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
37487cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka        for (SuggestedWordInfo wordInfo : suggestionsSet) {
375e8ef09567077211da034a77b457fd5f87e70f6f0Jean Chalard            LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
37687cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka        }
37787cecf7db61536d9f7ec07fe198d37a11b6c8407Satoshi Kataoka
378d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        final ArrayList<SuggestedWordInfo> suggestionsContainer =
3795f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSet);
380eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        final int suggestionsCount = suggestionsContainer.size();
3811eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
3821eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isAllUpperCase = wordComposer.isAllUpperCase();
383eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        if (isFirstCharCapitalized || isAllUpperCase) {
384eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            for (int i = 0; i < suggestionsCount; ++i) {
385eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
386eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
387eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                        wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
388eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                        0 /* trailingSingleQuotesCount */);
389eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                suggestionsContainer.set(i, transformedWordInfo);
390eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            }
391eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        }
392d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
393d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard        if (suggestionsContainer.size() > 1 && TextUtils.equals(suggestionsContainer.get(0).mWord,
394d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard                wordComposer.getRejectedBatchModeSuggestion())) {
395d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard            final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
396d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard            suggestionsContainer.add(1, rejected);
397d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard        }
398d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        SuggestedWordInfo.removeDups(suggestionsContainer);
399a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
400a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        // For some reason some suggestions with MIN_VALUE are making their way here.
401a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        // TODO: Find a more robust way to detect distractors.
402a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
403a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard            if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
404a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard                suggestionsContainer.remove(i);
405a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard            }
406a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        }
407a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
408eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // In the batch input mode, the most relevant suggested word should act as a "typed word"
409eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
4109666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada        callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer,
411d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                true /* typedWordValid */,
412eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                false /* willAutoCorrect */,
413d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isPunctuationSuggestions */,
414d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isObsoleteSuggestions */,
4159666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada                false /* isPrediction */));
416d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
417d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
4180d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
4197e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
4207e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        final SuggestedWordInfo typedWordInfo = suggestions.get(0);
4217e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        typedWordInfo.setDebugString("+");
4220d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        final int suggestionsSize = suggestions.size();
4230d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsList =
4245f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                CollectionUtils.newArrayList(suggestionsSize);
4257e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        suggestionsList.add(typedWordInfo);
4260d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // Note: i here is the index in mScores[], but the index in mSuggestions is one more
4270d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // than i because we added the typed word to mSuggestions without touching mScores.
4287e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        for (int i = 0; i < suggestionsSize - 1; ++i) {
4297e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final SuggestedWordInfo cur = suggestions.get(i + 1);
4300028ed3627ff4f37a62a80f3b2c857e373cd5090satok            final float normalizedScore = BinaryDictionary.calcNormalizedScore(
431db1939dbaa1de59eaf5693e2c89b02b323e9aac8satok                    typedWord, cur.toString(), cur.mScore);
4320d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            final String scoreInfoString;
4330d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            if (normalizedScore > 0) {
43494027c7201a376107a35ec78cd21db1905662601Tadashi G. Takaoka                scoreInfoString = String.format(
43594027c7201a376107a35ec78cd21db1905662601Tadashi G. Takaoka                        Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore);
4360d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            } else {
4377e518d8b8358c96b94b900f0917cdc5fd8190ce1satok                scoreInfoString = Integer.toString(cur.mScore);
4380d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            }
4397e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            cur.setDebugString(scoreInfoString);
4407e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            suggestionsList.add(cur);
4410d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        }
4420d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        return suggestionsList;
4430d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    }
4440d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard
445a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka    private static final class SuggestedWordInfoComparator
446a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka            implements Comparator<SuggestedWordInfo> {
4479da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        // This comparator ranks the word info with the higher frequency first. That's because
4489da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        // that's the order we want our elements in.
4499da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        @Override
4509da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
4519da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mScore > o2.mScore) return -1;
4529da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mScore < o2.mScore) return 1;
4539da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mCodePointCount < o2.mCodePointCount) return -1;
4549da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            if (o1.mCodePointCount > o2.mCodePointCount) return 1;
455bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            return o1.mWord.compareTo(o2.mWord);
4569da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard        }
4579da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard    }
4589da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard    private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
4599da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard            new SuggestedWordInfoComparator();
4609da0027b386c23b83c2f9b0121bc15fa15306e3aJean Chalard
461ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard    private static SuggestedWordInfo getTransformedSuggestedWordInfo(
462ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
463ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
4649011b89f4ea0d73f1ad78b2dd0a6557b950fddd9Jean Chalard        final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
465ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        if (isAllUpperCase) {
466bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            sb.append(wordInfo.mWord.toUpperCase(locale));
467ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        } else if (isFirstCharCapitalized) {
46899b93d17d53c2d587c45373831b327f7851ec0a8Jean Chalard            sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
469ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        } else {
470ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            sb.append(wordInfo.mWord);
471ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
472ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
473240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka            sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
474ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
475bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka        return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind,
476ef1e363016623ccf409c8c270f2c1e35a67734c9Jean Chalard                wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord);
477ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard    }
478ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard
47936fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani    public void close() {
4805f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka        final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
4816234be1fe76740c458781b633f4ac66edd8ea84fJean Chalard        dictionaries.addAll(mDictionaries.values());
482c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka        for (final Dictionary dictionary : dictionaries) {
483c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka            dictionary.close();
48436fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani        }
4856080f6878b10916013a8a5e1d5f58f8041452c56Kurt Partridge        mMainDictionary = null;
48636fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani    }
487923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
488