Suggest.java revision 3458d61807a03ed7fb8571488ee0fcbff39e07f8
1923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/*
2443c360d0afdbab091994244f045f4756feaf2b4Jean-Baptiste Queru * Copyright (C) 2008 The Android Open Source Project
3e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa *
4923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * use this file except in compliance with the License. You may obtain a copy of
6923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * the License at
7e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa *
8923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0
9e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa *
10923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * License for the specific language governing permissions and limitations under
14923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * the License.
15923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
16923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
17923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectpackage com.android.inputmethod.latin;
18923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
19923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport android.content.Context;
20923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport android.text.TextUtils;
21923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectimport android.util.Log;
22923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
23043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo;
24043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard
2533e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaokaimport java.io.File;
26fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaokaimport java.util.ArrayList;
27fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaokaimport java.util.Arrays;
28c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaokaimport java.util.HashMap;
29c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaokaimport java.util.HashSet;
30cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale;
31c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaokaimport java.util.Map;
32c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaokaimport java.util.Set;
33fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaoka
34923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/**
35e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * This class loads a dictionary and provides a list of suggestions for a given sequence of
36923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * characters. This includes corrections and completions.
37923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
38923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Projectpublic class Suggest implements Dictionary.WordCallback {
39923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
4082411d47ba7e8133ed2390c6920945e139a738cesatok    public static final String TAG = Suggest.class.getSimpleName();
41cdbbea735f590784791f0c1fe33a514c4e864836satok
42979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final int APPROX_MAX_WORD_LENGTH = 32;
43979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
44923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public static final int CORRECTION_NONE = 0;
45923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public static final int CORRECTION_BASIC = 1;
46923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public static final int CORRECTION_FULL = 2;
47979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final int CORRECTION_FULL_BIGRAM = 3;
48979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
49979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    /**
50979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     * Words that appear in both bigram and unigram data gets multiplier ranging from
51e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka     * BIGRAM_MULTIPLIER_MIN to BIGRAM_MULTIPLIER_MAX depending on the score from
52979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     * bigram data.
53979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     */
54979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final double BIGRAM_MULTIPLIER_MIN = 1.2;
55979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final double BIGRAM_MULTIPLIER_MAX = 1.5;
56979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
57979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    /**
58979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     * Maximum possible bigram frequency. Will depend on how many bits are being used in data
590c8d5ca023d54b7c9ef6c20eb7988288132bacb5Jean Chalard     * structure. Maximum bigram frequency will get the BIGRAM_MULTIPLIER_MAX as the multiplier.
60979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     */
61979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final int MAXIMUM_BIGRAM_FREQUENCY = 127;
62979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
63f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    // It seems the following values are only used for logging.
64979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final int DIC_USER_TYPED = 0;
65979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final int DIC_MAIN = 1;
66979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final int DIC_USER = 2;
67f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    public static final int DIC_USER_UNIGRAM = 3;
68979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public static final int DIC_CONTACTS = 4;
69f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    public static final int DIC_USER_BIGRAM = 5;
70979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    // If you add a type of dictionary, increment DIC_TYPE_LAST_ID
71f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    // TODO: this value seems unused. Remove it?
72f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    public static final int DIC_TYPE_LAST_ID = 5;
7334386e698876c0f29b2d5b54b44db1e32b562c47Amith Yamasani
74bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    public static final String DICT_KEY_MAIN = "main";
75bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    public static final String DICT_KEY_CONTACTS = "contacts";
76f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    // User dictionary, the system-managed one.
77bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    public static final String DICT_KEY_USER = "user";
78f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    // User unigram dictionary, internal to LatinIME
79f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    public static final String DICT_KEY_USER_UNIGRAM = "user_unigram";
80f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    // User bigram dictionary, internal to LatinIME
81bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
82bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    public static final String DICT_KEY_WHITELIST ="whitelist";
83bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
848553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard    private static final boolean DBG = LatinImeLogger.sDBG;
8582411d47ba7e8133ed2390c6920945e139a738cesatok
869f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok    private AutoCorrection mAutoCorrection;
879f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
884250eb27f54f8fedc388fe4825b0646a88778744Jean Chalard    private Dictionary mMainDict;
8914051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    private ContactsDictionary mContactsDict;
90bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    private WhitelistDictionary mWhiteListDictionary;
91c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka    private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
92c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka    private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
93979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
9486e815a142c8aa13213151e381a8a24ef23073d3Tadashi G. Takaoka    private int mPrefMaxSuggestions = 18;
9534386e698876c0f29b2d5b54b44db1e32b562c47Amith Yamasani
96979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    private static final int PREF_MAX_BIGRAMS = 60;
97979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
981b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka    private double mAutoCorrectionThreshold;
99e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka    private int[] mScores = new int[mPrefMaxSuggestions];
100e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka    private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
101979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
102bf0f4d9c8b789035255d6b89752c77801929002eAmith Yamasani    private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
103979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
104e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka    private CharSequence mTypedWord;
1050b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa
1060b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    // TODO: Remove these member variables by passing more context to addWord() callback method
1070b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    private boolean mIsFirstCharCapitalized;
1080b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa    private boolean mIsAllUpperCase;
109923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
110923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    private int mCorrectionMode = CORRECTION_BASIC;
111923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
1123af9f05f2916e376f265974c820c369a6c63a780Jean Chalard    public Suggest(final Context context, final int dictionaryResId, final Locale locale) {
1133af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        initAsynchronously(context, dictionaryResId, locale);
114979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
115979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
1163458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard    /* package for test */ Suggest(final Context context, final File dictionary,
1173458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard            final long startOffset, final long length, final Flag[] flagArray,
1183458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard            final Locale locale) {
1193af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        initSynchronously(null, DictionaryFactory.createDictionaryForTest(context, dictionary,
1203458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard                startOffset, length, flagArray), locale);
1219f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok    }
1229f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
1233458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard    private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
1243458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard        mWhiteListDictionary = new WhitelistDictionary(context, locale);
1253439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
1269f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok        mAutoCorrection = new AutoCorrection();
1274e01afc520da212b73804164d4d5a1c62239b02aJean Chalard        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
12833e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka    }
12933e0b1e79e464ac48a09433bbfcbb17ded620452Tadashi G. Takaoka
1303af9f05f2916e376f265974c820c369a6c63a780Jean Chalard    private void initAsynchronously(final Context context, final int dictionaryResId,
1313af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            final Locale locale) {
1323af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        resetMainDict(context, dictionaryResId, locale);
1333af9f05f2916e376f265974c820c369a6c63a780Jean Chalard
1343af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        // TODO: read the whitelist and init the pool asynchronously too.
1354e01afc520da212b73804164d4d5a1c62239b02aJean Chalard        // initPool should be done asynchronously now that the pool is thread-safe.
1363458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard        initWhitelistAndAutocorrectAndPool(context, locale);
1373af9f05f2916e376f265974c820c369a6c63a780Jean Chalard    }
1383af9f05f2916e376f265974c820c369a6c63a780Jean Chalard
1393458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard    private void initSynchronously(final Context context, final Dictionary mainDict,
1403458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard            final Locale locale) {
1413af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        mMainDict = mainDict;
1423af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict);
1433af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict);
1443458d61807a03ed7fb8571488ee0fcbff39e07f8Jean Chalard        initWhitelistAndAutocorrectAndPool(context, locale);
1453af9f05f2916e376f265974c820c369a6c63a780Jean Chalard    }
1463af9f05f2916e376f265974c820c369a6c63a780Jean Chalard
1473439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka    private void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key,
1483439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka            Dictionary dict) {
1493439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        final Dictionary oldDict = (dict == null)
1503439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka                ? dictionaries.remove(key)
1513439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka                : dictionaries.put(key, dict);
1523439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        if (oldDict != null && dict != oldDict) {
1533439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka            oldDict.close();
1543439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        }
1553439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka    }
1563439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka
1573af9f05f2916e376f265974c820c369a6c63a780Jean Chalard    public void resetMainDict(final Context context, final int dictionaryResId,
1583af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            final Locale locale) {
1593af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        mMainDict = null;
1603af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        new Thread("InitializeBinaryDictionary") {
161904baab25a4c6ec5d9c4bf7e562154e3f544d296satok            @Override
1623af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            public void run() {
1633af9f05f2916e376f265974c820c369a6c63a780Jean Chalard                final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager(
1643af9f05f2916e376f265974c820c369a6c63a780Jean Chalard                        context, locale, dictionaryResId);
1653af9f05f2916e376f265974c820c369a6c63a780Jean Chalard                mMainDict = newMainDict;
1663af9f05f2916e376f265974c820c369a6c63a780Jean Chalard                addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict);
1673af9f05f2916e376f265974c820c369a6c63a780Jean Chalard                addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict);
1683af9f05f2916e376f265974c820c369a6c63a780Jean Chalard            }
1693af9f05f2916e376f265974c820c369a6c63a780Jean Chalard        }.start();
170cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard    }
171cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalard
172923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public int getCorrectionMode() {
173923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        return mCorrectionMode;
174923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
17534386e698876c0f29b2d5b54b44db1e32b562c47Amith Yamasani
176923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public void setCorrectionMode(int mode) {
177923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        mCorrectionMode = mode;
178923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
179923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
180c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa    // The main dictionary could have been loaded asynchronously.  Don't cache the return value
181c769ef4dd17ff9561e99528624f74b9072a09fbbKen Wakasa    // of this method.
182e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani    public boolean hasMainDictionary() {
1834250eb27f54f8fedc388fe4825b0646a88778744Jean Chalard        return mMainDict != null;
184e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani    }
185e8f1edefeb2375a253d742c7f95e8d91677c7073Amith Yamasani
18614051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    public ContactsDictionary getContactsDictionary() {
18714051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        return mContactsDict;
18814051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    }
18914051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard
190bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    public Map<String, Dictionary> getUnigramDictionaries() {
191bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        return mUnigramDictionaries;
192bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    }
193bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
194979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public int getApproxMaxWordLength() {
195979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        return APPROX_MAX_WORD_LENGTH;
196979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
197979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
198923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
199923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
200f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard     * before the main dictionary, if set. This refers to the system-managed user dictionary.
201923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
202923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public void setUserDictionary(Dictionary userDictionary) {
2033439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary);
204923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
2052bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer
2062bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    /**
207699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove
208699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * the contacts dictionary by passing null to this method. In this case no contacts dictionary
209699094f9b6e0a4621e8b3cfab70b59c0c7c086bbJean Chalard     * won't be used.
2102bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer     */
21114051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard    public void setContactsDictionary(ContactsDictionary contactsDictionary) {
21214051e2b5343db4b0531b7b4b806da0c09d6e251Jean Chalard        mContactsDict = contactsDictionary;
2133439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
2143439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
2152bed1531c2c9bd48096bfa97dd1a39e04bd15e7bEric Fischer    }
216e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa
217f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard    public void setUserUnigramDictionary(Dictionary userUnigramDictionary) {
218f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_UNIGRAM, userUnigramDictionary);
21934386e698876c0f29b2d5b54b44db1e32b562c47Amith Yamasani    }
220923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
221979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    public void setUserBigramDictionary(Dictionary userBigramDictionary) {
2223439c72639d50921a87ab6f9d3aa1bf941aef8d2Tadashi G. Takaoka        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_BIGRAM, userBigramDictionary);
223979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
224979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
2251b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka    public void setAutoCorrectionThreshold(double threshold) {
2261b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka        mAutoCorrectionThreshold = threshold;
227b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda    }
228b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda
22914e427d5bb13d59d23fb317ef90a6c44ae279425satok    public boolean isAggressiveAutoCorrectionMode() {
23014e427d5bb13d59d23fb317ef90a6c44ae279425satok        return (mAutoCorrectionThreshold == 0);
23114e427d5bb13d59d23fb317ef90a6c44ae279425satok    }
23214e427d5bb13d59d23fb317ef90a6c44ae279425satok
233923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
234923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * Number of suggestions to generate from the input key sequence. This has
235923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * to be a number between 1 and 100 (inclusive).
236923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @param maxSuggestions
237923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     * @throws IllegalArgumentException if the number is out of range
238923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
239923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public void setMaxSuggestions(int maxSuggestions) {
240923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        if (maxSuggestions < 1 || maxSuggestions > 100) {
241923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
242923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
243923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        mPrefMaxSuggestions = maxSuggestions;
244e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        mScores = new int[mPrefMaxSuggestions];
245e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        mBigramScores = new int[PREF_MAX_BIGRAMS];
246979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        collectGarbage(mSuggestions, mPrefMaxSuggestions);
2474e01afc520da212b73804164d4d5a1c62239b02aJean Chalard        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
248923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
24963fa90a7910d9f43f27a0bf9a6702f8fb44ce3e7Amith Yamasani
250923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    /**
2517e181fe1010c8eac7814cc67a0c4b3864a10b151Tadashi G. Takaoka     * Returns a object which represents suggested words that match the list of character codes
2527e181fe1010c8eac7814cc67a0c4b3864a10b151Tadashi G. Takaoka     * passed in. This object contents will be overwritten the next time this function is called.
253979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     * @param wordComposer contains what is currently being typed
254979f8690967ff5409fe18f5085858ccdb8e0ccf1satok     * @param prevWordForBigram previous word (used only for bigram)
2557e181fe1010c8eac7814cc67a0c4b3864a10b151Tadashi G. Takaoka     * @return suggested words object.
256923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project     */
257904baab25a4c6ec5d9c4bf7e562154e3f544d296satok    public SuggestedWords getSuggestions(final WordComposer wordComposer,
258043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard            final CharSequence prevWordForBigram, final ProximityInfo proximityInfo) {
259904baab25a4c6ec5d9c4bf7e562154e3f544d296satok        return getSuggestedWordBuilder(wordComposer, prevWordForBigram,
260043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard                proximityInfo).build();
2617e181fe1010c8eac7814cc67a0c4b3864a10b151Tadashi G. Takaoka    }
2627e181fe1010c8eac7814cc67a0c4b3864a10b151Tadashi G. Takaoka
263bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
264bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        if (TextUtils.isEmpty(word) || !(all || first)) return word;
265bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        final int wordLength = word.length();
2664e01afc520da212b73804164d4d5a1c62239b02aJean Chalard        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
26735f20916e5348d7fa485ba8eb0a5cf2e67f4f354Tadashi G. Takaoka        // TODO: Must pay attention to locale when changing case.
268bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        if (all) {
269bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok            sb.append(word.toString().toUpperCase());
270bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        } else if (first) {
271bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok            sb.append(Character.toUpperCase(word.charAt(0)));
272bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok            if (wordLength > 1) {
273bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok                sb.append(word.subSequence(1, wordLength));
274bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok            }
275bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        }
276bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        return sb;
277bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok    }
278bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
27989bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard    protected void addBigramToSuggestions(CharSequence bigram) {
280a6e912cf9849f5c979303042ce83820a8dc560d0Jean Chalard        // TODO: Try to be a little more shrewd with resource allocation.
281a6e912cf9849f5c979303042ce83820a8dc560d0Jean Chalard        // At the moment we copy this object because the StringBuilders are pooled (see
282a6e912cf9849f5c979303042ce83820a8dc560d0Jean Chalard        // StringBuilderPool.java) and when we are finished using mSuggestions and
283a6e912cf9849f5c979303042ce83820a8dc560d0Jean Chalard        // mBigramSuggestions we will take everything from both and insert them back in the
284a6e912cf9849f5c979303042ce83820a8dc560d0Jean Chalard        // pool, so we can't allow the same object to be in both lists at the same time.
285a6e912cf9849f5c979303042ce83820a8dc560d0Jean Chalard        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
286a6e912cf9849f5c979303042ce83820a8dc560d0Jean Chalard        sb.append(bigram);
287a6e912cf9849f5c979303042ce83820a8dc560d0Jean Chalard        mSuggestions.add(sb);
28889bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard    }
28989bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard
2907e181fe1010c8eac7814cc67a0c4b3864a10b151Tadashi G. Takaoka    // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
291904baab25a4c6ec5d9c4bf7e562154e3f544d296satok    public SuggestedWords.Builder getSuggestedWordBuilder(
292043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard            final WordComposer wordComposer, CharSequence prevWordForBigram,
293043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard            final ProximityInfo proximityInfo) {
294979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        LatinImeLogger.onStartSuggestion(prevWordForBigram);
2959f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok        mAutoCorrection.init();
2960b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa        mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
2970b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa        mIsAllUpperCase = wordComposer.isAllUpperCase();
298979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        collectGarbage(mSuggestions, mPrefMaxSuggestions);
299e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        Arrays.fill(mScores, 0);
3001b62ff1a3d61cd44ab88acdfcbdf0fc70a7e1b10Amith Yamasani
301923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        // Save a lowercase version of the original word
3025c08151c227d98031abe27c3f0a8f43a7126ae9dJean Chalard        String typedWord = wordComposer.getTypedWord();
3039ecad8c2e8571ece6f3f7fbb19ceda5be7866cf0Tadashi G. Takaoka        if (typedWord != null) {
304979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            // Treating USER_TYPED as UNIGRAM suggestion for logging now.
3055c08151c227d98031abe27c3f0a8f43a7126ae9dJean Chalard            LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
306979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    Dictionary.DataType.UNIGRAM);
307923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
308e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        mTypedWord = typedWord;
309979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
31089bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard        if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
311979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                || mCorrectionMode == CORRECTION_BASIC)) {
312979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            // At first character typed, search only the bigrams
313e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            Arrays.fill(mBigramScores, 0);
314979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
315979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
316979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            if (!TextUtils.isEmpty(prevWordForBigram)) {
317979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
318e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa                if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
319979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    prevWordForBigram = lowerPrevWord;
320979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                }
321c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka                for (final Dictionary dictionary : mBigramDictionaries.values()) {
322c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka                    dictionary.getBigrams(wordComposer, prevWordForBigram, this);
323979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                }
32489bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                if (TextUtils.isEmpty(typedWord)) {
32589bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    // Nothing entered: return all bigrams for the previous word
32689bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
32789bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    for (int i = 0; i < insertCount; ++i) {
32889bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                        addBigramToSuggestions(mBigramSuggestions.get(i));
32989bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    }
33089bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                } else {
33189bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    // Word entered: return only bigrams that match the first char of the typed word
332904baab25a4c6ec5d9c4bf7e562154e3f544d296satok                    @SuppressWarnings("null")
33389bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    final char currentChar = typedWord.charAt(0);
33435f20916e5348d7fa485ba8eb0a5cf2e67f4f354Tadashi G. Takaoka                    // TODO: Must pay attention to locale when changing case.
33589bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    final char currentCharUpper = Character.toUpperCase(currentChar);
33689bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    int count = 0;
33789bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    final int bigramSuggestionSize = mBigramSuggestions.size();
33889bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                    for (int i = 0; i < bigramSuggestionSize; i++) {
33989bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                        final CharSequence bigramSuggestion = mBigramSuggestions.get(i);
34089bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                        final char bigramSuggestionFirstChar = bigramSuggestion.charAt(0);
34189bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                        if (bigramSuggestionFirstChar == currentChar
34289bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                                || bigramSuggestionFirstChar == currentCharUpper) {
34389bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                            addBigramToSuggestions(bigramSuggestion);
34489bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                            if (++count > mPrefMaxSuggestions) break;
34589bd776cf68150202d774d62cc1c88664aea5e9fJean Chalard                        }
346979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    }
347979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                }
348979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            }
349979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
350979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        } else if (wordComposer.size() > 1) {
351979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            // At second character typed, search the unigrams (scores being affected by bigrams)
352c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka            for (final String key : mUnigramDictionaries.keySet()) {
353f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard                // Skip UserUnigramDictionary and WhitelistDictionary to lookup
354f4223452119f9ff8b52f026f7ef92d961736dc51Jean Chalard                if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
355c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka                    continue;
356c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka                final Dictionary dictionary = mUnigramDictionaries.get(key);
357043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard                dictionary.getWords(wordComposer, this, proximityInfo);
358923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            }
359923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
3609f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok        final String typedWordString = typedWord == null ? null : typedWord.toString();
3619f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
362bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized,
363bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok                mWhiteListDictionary.getWhiteListedWord(typedWordString));
364bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
365bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
366e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
367904baab25a4c6ec5d9c4bf7e562154e3f544d296satok                whitelistedWord);
3689f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
369bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        if (whitelistedWord != null) {
370bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok            mSuggestions.add(0, whitelistedWord);
371bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok        }
372bcfce3b3b9dbd4f5db736948b74bd820fc639a08satok
3739f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok        if (typedWord != null) {
3749f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok            mSuggestions.add(0, typedWordString);
3759f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok        }
3766da8b74582b1c70cae02558c605c5a224329cf7aJean Chalard        Utils.removeDupes(mSuggestions);
3779f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
3788553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard        if (DBG) {
3799f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok            double normalizedScore = mAutoCorrection.getNormalizedScore();
380e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
3818553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard                    new ArrayList<SuggestedWords.SuggestedWordInfo>();
382e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
383e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            for (int i = 0; i < mScores.length; ++i) {
3848553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard                if (normalizedScore > 0) {
385071f47140cec02197de5e163f45c77990b39457dTadashi G. Takaoka                    final String scoreThreshold = String.format("%d (%4.2f)", mScores[i],
386071f47140cec02197de5e163f45c77990b39457dTadashi G. Takaoka                            normalizedScore);
387e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                    scoreInfoList.add(
388e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                            new SuggestedWords.SuggestedWordInfo(scoreThreshold, false));
3898553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard                    normalizedScore = 0.0;
3908553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard                } else {
391e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                    final String score = Integer.toString(mScores[i]);
392e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                    scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false));
3938553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard                }
3948553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard            }
395e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            for (int i = mScores.length; i < mSuggestions.size(); ++i) {
396e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
3978553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard            }
398e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList);
3998553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard        }
4009f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok        return new SuggestedWords.Builder().addWords(mSuggestions, null);
401923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
402923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
4036f7218627eda110a8454053f8ecb7b80edfdc8cesatok    public boolean hasAutoCorrection() {
4049f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok        return mAutoCorrection.hasAutoCorrection();
405923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
406923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
4075a309f57155fb95667c2ccdda730eaf175de8876Tadashi G. Takaoka    @Override
408e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka    public boolean addWord(final char[] word, final int offset, final int length, int score,
409979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            final int dicTypeId, final Dictionary.DataType dataType) {
410979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        Dictionary.DataType dataTypeForLog = dataType;
411e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        final ArrayList<CharSequence> suggestions;
412e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        final int[] sortedScores;
413e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        final int prefMaxSuggestions;
414979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        if(dataType == Dictionary.DataType.BIGRAM) {
415979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            suggestions = mBigramSuggestions;
416e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            sortedScores = mBigramScores;
417979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            prefMaxSuggestions = PREF_MAX_BIGRAMS;
418979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        } else {
419979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            suggestions = mSuggestions;
420e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            sortedScores = mScores;
421979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            prefMaxSuggestions = mPrefMaxSuggestions;
422979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        }
423979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
424923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        int pos = 0;
425979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
426923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        // Check if it's the same word, only caps are different
427e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
428d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard            // TODO: remove this surrounding if clause and move this logic to
429d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard            // getSuggestedWordBuilder.
430d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard            if (suggestions.size() > 0) {
431e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                final String currentHighestWord = suggestions.get(0).toString();
432d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard                // If the current highest word is also equal to typed word, we need to compare
433d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard                // frequency to determine the insertion position. This does not ensure strictly
434d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard                // correct ordering, but ensures the top score is on top which is enough for
435d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard                // removing duplicates correctly.
436e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length)
437e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                        && score <= sortedScores[0]) {
438d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard                    pos = 1;
439d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard                }
440d631651b1291aef52bdd6ea7caaf9b95c9704506Jean Chalard            }
441923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        } else {
442979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            if (dataType == Dictionary.DataType.UNIGRAM) {
443979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                // Check if the word was already added before (by bigram data)
444979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                int bigramSuggestion = searchBigramSuggestion(word,offset,length);
445979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                if(bigramSuggestion >= 0) {
446979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    dataTypeForLog = Dictionary.DataType.BIGRAM;
447979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    // turn freq from bigram into multiplier specified above
448e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                    double multiplier = (((double) mBigramScores[bigramSuggestion])
449979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                            / MAXIMUM_BIGRAM_FREQUENCY)
450979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                            * (BIGRAM_MULTIPLIER_MAX - BIGRAM_MULTIPLIER_MIN)
451979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                            + BIGRAM_MULTIPLIER_MIN;
452979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    /* Log.d(TAG,"bigram num: " + bigramSuggestion
453979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                            + "  wordB: " + mBigramSuggestions.get(bigramSuggestion).toString()
454e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                            + "  currentScore: " + score + "  bigramScore: "
455e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                            + mBigramScores[bigramSuggestion]
456979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                            + "  multiplier: " + multiplier); */
457e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                    score = (int)Math.round((score * multiplier));
458979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                }
459979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            }
460979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
461e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            // Check the last one's score and bail
462e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka            if (sortedScores[prefMaxSuggestions - 1] >= score) return true;
463923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            while (pos < prefMaxSuggestions) {
464e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                if (sortedScores[pos] < score
465e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka                        || (sortedScores[pos] == score && length < suggestions.get(pos).length())) {
466923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project                    break;
467923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project                }
468923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project                pos++;
469923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            }
470923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
471923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        if (pos >= prefMaxSuggestions) {
472923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            return true;
473923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
474979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
475e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
476e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9Tadashi G. Takaoka        sortedScores[pos] = score;
4774e01afc520da212b73804164d4d5a1c62239b02aJean Chalard        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
47835f20916e5348d7fa485ba8eb0a5cf2e67f4f354Tadashi G. Takaoka        // TODO: Must pay attention to locale when changing case.
4790b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa        if (mIsAllUpperCase) {
4800b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa            sb.append(new String(word, offset, length).toUpperCase());
4810b4ae1f578e768eec4ada90aeb81d11acb10eb2eKen Wakasa        } else if (mIsFirstCharCapitalized) {
482359f168161074acb11fcd7a443c91fe3cc010e1dAmith Yamasani            sb.append(Character.toUpperCase(word[offset]));
483359f168161074acb11fcd7a443c91fe3cc010e1dAmith Yamasani            if (length > 1) {
484359f168161074acb11fcd7a443c91fe3cc010e1dAmith Yamasani                sb.append(word, offset + 1, length - 1);
485359f168161074acb11fcd7a443c91fe3cc010e1dAmith Yamasani            }
486359f168161074acb11fcd7a443c91fe3cc010e1dAmith Yamasani        } else {
487359f168161074acb11fcd7a443c91fe3cc010e1dAmith Yamasani            sb.append(word, offset, length);
488359f168161074acb11fcd7a443c91fe3cc010e1dAmith Yamasani        }
489979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        suggestions.add(pos, sb);
490979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        if (suggestions.size() > prefMaxSuggestions) {
4914e01afc520da212b73804164d4d5a1c62239b02aJean Chalard            final CharSequence garbage = suggestions.remove(prefMaxSuggestions);
492923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            if (garbage instanceof StringBuilder) {
4934e01afc520da212b73804164d4d5a1c62239b02aJean Chalard                StringBuilderPool.recycle((StringBuilder)garbage);
494923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            }
495979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        } else {
496979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
497923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
498923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        return true;
499923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
500923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
501979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    private int searchBigramSuggestion(final char[] word, final int offset, final int length) {
502979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        // TODO This is almost O(n^2). Might need fix.
503979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        // search whether the word appeared in bigram data
504979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        int bigramSuggestSize = mBigramSuggestions.size();
505979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        for(int i = 0; i < bigramSuggestSize; i++) {
506979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            if(mBigramSuggestions.get(i).length() == length) {
507979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                boolean chk = true;
508979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                for(int j = 0; j < length; j++) {
509979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    if(mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
510979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                        chk = false;
511979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                        break;
512979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                    }
513979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                }
514979f8690967ff5409fe18f5085858ccdb8e0ccf1satok                if(chk) return i;
515979f8690967ff5409fe18f5085858ccdb8e0ccf1satok            }
516979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        }
517979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
518979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        return -1;
519979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    }
520979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
521979f8690967ff5409fe18f5085858ccdb8e0ccf1satok    private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
5224e01afc520da212b73804164d4d5a1c62239b02aJean Chalard        int poolSize = StringBuilderPool.getSize();
523979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        int garbageSize = suggestions.size();
524979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        while (poolSize < prefMaxSuggestions && garbageSize > 0) {
5254e01afc520da212b73804164d4d5a1c62239b02aJean Chalard            final CharSequence garbage = suggestions.get(garbageSize - 1);
5264e01afc520da212b73804164d4d5a1c62239b02aJean Chalard            if (garbage instanceof StringBuilder) {
5274e01afc520da212b73804164d4d5a1c62239b02aJean Chalard                StringBuilderPool.recycle((StringBuilder)garbage);
528923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project                poolSize++;
529923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            }
530923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            garbageSize--;
531923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
532979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        if (poolSize == prefMaxSuggestions + 1) {
533923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project            Log.w("Suggest", "String pool got too big: " + poolSize);
534923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project        }
535979f8690967ff5409fe18f5085858ccdb8e0ccf1satok        suggestions.clear();
536923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
53736fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani
53836fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani    public void close() {
539c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka        final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
540c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka        dictionaries.addAll(mUnigramDictionaries.values());
541c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka        dictionaries.addAll(mBigramDictionaries.values());
542c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka        for (final Dictionary dictionary : dictionaries) {
543c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka            dictionary.close();
54436fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani        }
545c2c44f94e705e74598ec944ab51f3bd13eb50dbfTadashi G. Takaoka        mMainDict = null;
54636fcf25833f7c8876cbc8286e0c159b052dc2626Amith Yamasani    }
547923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
548