Suggest.java revision 2dae79b1966a7970c25c8b79beec1c95c13f6c87
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.text.TextUtils;
20923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
21043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalardimport com.android.inputmethod.keyboard.ProximityInfo;
22def4551c2a570e7f575b2e9303506d790c2f335fJean Chalardimport com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
232dae79b1966a7970c25c8b79beec1c95c13f6c87Tadashi G. Takaokaimport com.android.inputmethod.latin.define.DebugFlags;
24b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagiimport com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
25b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasaimport com.android.inputmethod.latin.utils.AutoCorrectionUtils;
26e784148ae6872942434eaa55ca32b4c6442cc8e8Keisuke Kuroyanagiimport com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
27e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.StringUtils;
28adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagiimport com.android.inputmethod.latin.utils.SuggestionResults;
29043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard
30fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaokaimport java.util.ArrayList;
31cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale;
32fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaoka
33923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/**
34e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * This class loads a dictionary and provides a list of suggestions for a given sequence of
35923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * characters. This includes corrections and completions.
36923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
37a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class Suggest {
3882411d47ba7e8133ed2390c6920945e139a738cesatok    public static final String TAG = Suggest.class.getSimpleName();
39cdbbea735f590784791f0c1fe33a514c4e864836satok
40f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    // Session id for
41bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
42f1233b58c2d81b575c92339f146cfe0f73a992faKeisuke Kuroyanagi    // We are sharing the same ID between typing and gesture to save RAM footprint.
43f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    public static final int SESSION_TYPING = 0;
44f1233b58c2d81b575c92339f146cfe0f73a992faKeisuke Kuroyanagi    public static final int SESSION_GESTURE = 0;
45f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka
465475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard    // TODO: rename this to CORRECTION_OFF
47923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    public static final int CORRECTION_NONE = 0;
485475e92b3fb33dd7d6b021ddcbe1ca593112b5c8Jean Chalard    // TODO: rename this to CORRECTION_ON
494606de117b7541125f3f15bd6b50d77ed20e5132Jean Chalard    public static final int CORRECTION_FULL = 1;
50979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
51a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard    // Close to -2**31
52a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard    private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
53a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
542dae79b1966a7970c25c8b79beec1c95c13f6c87Tadashi G. Takaoka    private static final boolean DBG = DebugFlags.DEBUG_ENABLED;
55a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi    private final DictionaryFacilitator mDictionaryFacilitator;
56979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
570028ed3627ff4f37a62a80f3b2c857e373cd5090satok    private float mAutoCorrectionThreshold;
58979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
59a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi    public Suggest(final DictionaryFacilitator dictionaryFacilitator) {
60a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi        mDictionaryFacilitator = dictionaryFacilitator;
61a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi    }
62a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi
63adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi    public Locale getLocale() {
64adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi        return mDictionaryFacilitator.getLocale();
65366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka    }
66366c0c5198f43279f4671a196556124f41297c0cSatoshi Kataoka
67adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi    public void setAutoCorrectionThreshold(final float threshold) {
681b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka        mAutoCorrectionThreshold = threshold;
69b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda    }
70b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda
719666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    public interface OnGetSuggestedWordsCallback {
729666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada        public void onGetSuggestedWords(final SuggestedWords suggestedWords);
739666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    }
749666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada
759666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    public void getSuggestedWords(final WordComposer wordComposer,
7683c40a2301a0b5a42a75eecada48e7887a7c940eKeisuke Kuroyanagi            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
77b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi            final SettingsValuesForSuggestion settingsValuesForSuggestion,
78b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi            final boolean isCorrectionEnabled, final int sessionId, final int sequenceNumber,
799666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada            final OnGetSuggestedWordsCallback callback) {
80d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        if (wordComposer.isBatchMode()) {
8183c40a2301a0b5a42a75eecada48e7887a7c940eKeisuke Kuroyanagi            getSuggestedWordsForBatchInput(wordComposer, prevWordsInfo, proximityInfo,
82b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi                    settingsValuesForSuggestion, sessionId, sequenceNumber, callback);
83d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        } else {
8483c40a2301a0b5a42a75eecada48e7887a7c940eKeisuke Kuroyanagi            getSuggestedWordsForTypingInput(wordComposer, prevWordsInfo, proximityInfo,
85b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi                    settingsValuesForSuggestion, isCorrectionEnabled, sequenceNumber, callback);
86d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        }
87d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
88d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
89e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard    private static ArrayList<SuggestedWordInfo> getTransformedSuggestedWordInfoList(
90e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            final WordComposer wordComposer, final SuggestionResults results,
91e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            final int trailingSingleQuotesCount) {
92e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final boolean shouldMakeSuggestionsAllUpperCase = wordComposer.isAllUpperCase()
93e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                && !wordComposer.isResumed();
94e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final boolean isOnlyFirstCharCapitalized =
95e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                wordComposer.isOrWillBeOnlyFirstCharCapitalized();
96e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard
97e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final ArrayList<SuggestedWordInfo> suggestionsContainer = new ArrayList<>(results);
98e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final int suggestionsCount = suggestionsContainer.size();
99e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        if (isOnlyFirstCharCapitalized || shouldMakeSuggestionsAllUpperCase
100e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                || 0 != trailingSingleQuotesCount) {
101e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            for (int i = 0; i < suggestionsCount; ++i) {
102e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
103e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
104e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                        wordInfo, results.mLocale, shouldMakeSuggestionsAllUpperCase,
105e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                        isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
106e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                suggestionsContainer.set(i, transformedWordInfo);
107e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            }
108e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        }
109e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        return suggestionsContainer;
110e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard    }
111e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard
112e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard    private static String getWhitelistedWordOrNull(final ArrayList<SuggestedWordInfo> suggestions) {
113e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        if (suggestions.isEmpty()) {
114e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            return null;
115e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        }
116e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final SuggestedWordInfo firstSuggestedWordInfo = suggestions.get(0);
117e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
118e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            return null;
119e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        }
120e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        return firstSuggestedWordInfo.mWord;
121e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard    }
122e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard
1239666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // Retrieves suggestions for the typing input
1249666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // and calls the callback function with the suggestions.
1259666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    private void getSuggestedWordsForTypingInput(final WordComposer wordComposer,
12683c40a2301a0b5a42a75eecada48e7887a7c940eKeisuke Kuroyanagi            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
127b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi            final SettingsValuesForSuggestion settingsValuesForSuggestion,
128b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi            final boolean isCorrectionEnabled, final int sequenceNumber,
129c130be877987afe675869d2cbea9d5a49b4ad419Jean Chalard            final OnGetSuggestedWordsCallback callback) {
130c83359f9746ca6f0269a1a7017b585c1a5cab9b8Jean Chalard        final String typedWord = wordComposer.getTypedWord();
13134873a66f03e0b9945474213fa2bc48cc272a7caKeisuke Kuroyanagi        final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(typedWord);
13210abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard        final String consideredWord = trailingSingleQuotesCount > 0
13310abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard                ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
134117fc18ed46496c81596f8207bba30a09c7317d1Jean Chalard                : typedWord;
135979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
136adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi        final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
137b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi                wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion,
138b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi                SESSION_TYPING);
1391ec3f158d2532d71c8660f02710b342c7a16f330Jean Chalard        final ArrayList<SuggestedWordInfo> suggestionsContainer =
140e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
141e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                        trailingSingleQuotesCount);
142b740886aeb47f8f3fb32bca7a7faa13d5756bd74Jean Chalard        final boolean didRemoveTypedWord =
143e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                SuggestedWordInfo.removeDups(wordComposer.getTypedWord(), suggestionsContainer);
1441ec3f158d2532d71c8660f02710b342c7a16f330Jean Chalard
145e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final String whitelistedWord = getWhitelistedWordOrNull(suggestionsContainer);
146e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final boolean resultsArePredictions = !wordComposer.isComposingWord();
1477b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard
148b740886aeb47f8f3fb32bca7a7faa13d5756bd74Jean Chalard        // We allow auto-correction if we have a whitelisted word, or if the word had more than
149b740886aeb47f8f3fb32bca7a7faa13d5756bd74Jean Chalard        // one char and was not suggested.
150b740886aeb47f8f3fb32bca7a7faa13d5756bd74Jean Chalard        final boolean allowsToBeAutoCorrected = (null != whitelistedWord)
151b740886aeb47f8f3fb32bca7a7faa13d5756bd74Jean Chalard                || (consideredWord.length() > 1 && !didRemoveTypedWord);
152caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard
15367af2a24157ead953607bdfd585fba3a7e6bf50cJean Chalard        final boolean hasAutoCorrection;
154966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
155966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // any attempt to do auto-correction is already shielded with a test for this flag; at the
156966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // same time, it feels wrong that the SuggestedWord object includes information about
157966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // the current settings. It may also be useful to know, when the setting is off, whether
158966efe48891cbdd364d94f1e72fa0435ab8f2b77Jean Chalard        // the word *would* have been auto-corrected.
159e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        if (!isCorrectionEnabled || !allowsToBeAutoCorrected || resultsArePredictions
160e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                || suggestionResults.isEmpty() || wordComposer.hasDigits()
161a374482719ef9f38395e7e39884433cc72e9cdb7Keisuke Kuroyanagi                || wordComposer.isMostlyCaps() || wordComposer.isResumed()
162adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi                || !mDictionaryFacilitator.hasInitializedMainDictionary()
163e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
16490d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // If we don't have a main dictionary, we never want to auto-correct. The reason for
16590d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // this is, the user may have a contact whose name happens to match a valid word in
16690d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // their language, and it will unexpectedly auto-correct. For example, if the user
16790d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // types in English with no dictionary and has a "Will" in their contact list, "will"
16890d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no
16990d300c770b1697af5b715e55fa87d97e22588d2Jean Chalard            // auto-correct.
170f309f2288b0652df0b611ed3f86897c939e68d63Jean Chalard            // Also, shortcuts should never auto-correct unless they are whitelist entries.
171f309f2288b0652df0b611ed3f86897c939e68d63Jean Chalard            // TODO: we may want to have shortcut-only entries auto-correct in the future.
172ea578f6b1dbcf04ffcc9c673f72a38ed2cfecdfcJean Chalard            hasAutoCorrection = false;
17394b20c90d86aa042c2f361597665045271956decJean Chalard        } else {
174b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasa            hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
175e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                    suggestionResults.first(), consideredWord, mAutoCorrectionThreshold);
17694b20c90d86aa042c2f361597665045271956decJean Chalard        }
1779f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
178f89a75134b03bd2675c85249a184c09f83c6f80cJean Chalard        if (!TextUtils.isEmpty(typedWord)) {
179bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
18024eec0fa680f97e64d1fa0df754acbad95ed9a76Jean Chalard                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
181e8ef09567077211da034a77b457fd5f87e70f6f0Jean Chalard                    Dictionary.DICTIONARY_USER_TYPED,
18224aad5a4d545e743fe43953c1a9d8141c022d355Jean Chalard                    SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
18324aad5a4d545e743fe43953c1a9d8141c022d355Jean Chalard                    SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
18428eeb35d149468514a65379e9d0d1672cf26981eJean Chalard        }
1859f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
186def4551c2a570e7f575b2e9303506d790c2f335fJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsList;
187bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard        if (DBG && !suggestionsContainer.isEmpty()) {
188bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer);
189ed9986824e1339855376771ad29fae4de921a029Jean Chalard        } else {
190bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = suggestionsContainer;
1918553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard        }
192ed9986824e1339855376771ad29fae4de921a029Jean Chalard
193e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard        callback.onGetSuggestedWords(new SuggestedWords(suggestionsList,
194e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard                suggestionResults.mRawSuggestions,
195b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // TODO: this first argument is lying. If this is a whitelisted word which is an
196b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // actual word, it says typedWordValid = false, which looks wrong. We should either
197b3cfde2cbb96951b1202c70b9961f340bdf495d0Jean Chalard                // rename the attribute or change the value.
198e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                !resultsArePredictions && !allowsToBeAutoCorrected /* typedWordValid */,
199e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard                hasAutoCorrection /* willAutoCorrect */,
200e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                false /* isObsoleteSuggestions */, resultsArePredictions, sequenceNumber));
201923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
202923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
2039666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // Retrieves suggestions for the batch input
2049666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // and calls the callback function with the suggestions.
2059666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
20683c40a2301a0b5a42a75eecada48e7887a7c940eKeisuke Kuroyanagi            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
207b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi            final SettingsValuesForSuggestion settingsValuesForSuggestion,
208c130be877987afe675869d2cbea9d5a49b4ad419Jean Chalard            final int sessionId, final int sequenceNumber,
209c130be877987afe675869d2cbea9d5a49b4ad419Jean Chalard            final OnGetSuggestedWordsCallback callback) {
210adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi        final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
211b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi                wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion, sessionId);
212d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        final ArrayList<SuggestedWordInfo> suggestionsContainer =
213a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka                new ArrayList<>(suggestionResults);
214eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        final int suggestionsCount = suggestionsContainer.size();
2151eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
2161eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isAllUpperCase = wordComposer.isAllUpperCase();
217eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        if (isFirstCharCapitalized || isAllUpperCase) {
218eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            for (int i = 0; i < suggestionsCount; ++i) {
219eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
220eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
221adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi                        wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
222eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                        0 /* trailingSingleQuotesCount */);
223eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                suggestionsContainer.set(i, transformedWordInfo);
224eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            }
225eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        }
226d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
227d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard        if (suggestionsContainer.size() > 1 && TextUtils.equals(suggestionsContainer.get(0).mWord,
228d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard                wordComposer.getRejectedBatchModeSuggestion())) {
229d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard            final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
230d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard            suggestionsContainer.add(1, rejected);
231d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard        }
232fdebf4005f849a4a2875b686d239a817ca043842Jean Chalard        SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer);
233a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
234a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        // For some reason some suggestions with MIN_VALUE are making their way here.
235a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        // TODO: Find a more robust way to detect distractors.
236a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
237a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard            if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
238a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard                suggestionsContainer.remove(i);
239a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard            }
240a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        }
241a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
242eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // In the batch input mode, the most relevant suggested word should act as a "typed word"
243eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
244e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard        callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer,
245e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard                suggestionResults.mRawSuggestions,
246d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                true /* typedWordValid */,
247eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                false /* willAutoCorrect */,
248d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isObsoleteSuggestions */,
249c130be877987afe675869d2cbea9d5a49b4ad419Jean Chalard                false /* isPrediction */, sequenceNumber));
250d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
251d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
2520d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
2537e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
2547e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        final SuggestedWordInfo typedWordInfo = suggestions.get(0);
2557e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        typedWordInfo.setDebugString("+");
2560d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        final int suggestionsSize = suggestions.size();
257a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(suggestionsSize);
2587e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        suggestionsList.add(typedWordInfo);
2590d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // Note: i here is the index in mScores[], but the index in mSuggestions is one more
2600d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // than i because we added the typed word to mSuggestions without touching mScores.
2617e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        for (int i = 0; i < suggestionsSize - 1; ++i) {
2627e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final SuggestedWordInfo cur = suggestions.get(i + 1);
263e784148ae6872942434eaa55ca32b4c6442cc8e8Keisuke Kuroyanagi            final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
264db1939dbaa1de59eaf5693e2c89b02b323e9aac8satok                    typedWord, cur.toString(), cur.mScore);
2650d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            final String scoreInfoString;
2660d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            if (normalizedScore > 0) {
26794027c7201a376107a35ec78cd21db1905662601Tadashi G. Takaoka                scoreInfoString = String.format(
2682fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                        Locale.ROOT, "%d (%4.2f), %s", cur.mScore, normalizedScore,
2692fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                        cur.mSourceDict.mDictType);
2700d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            } else {
2717e518d8b8358c96b94b900f0917cdc5fd8190ce1satok                scoreInfoString = Integer.toString(cur.mScore);
2720d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            }
2737e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            cur.setDebugString(scoreInfoString);
2747e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            suggestionsList.add(cur);
2750d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        }
2760d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        return suggestionsList;
2770d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    }
2780d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard
279e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard    /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
280ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
281367c199de16f7ce8e608bdf38bf35df8995e18a0Jean Chalard            final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) {
2829011b89f4ea0d73f1ad78b2dd0a6557b950fddd9Jean Chalard        final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
283ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        if (isAllUpperCase) {
284bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            sb.append(wordInfo.mWord.toUpperCase(locale));
285367c199de16f7ce8e608bdf38bf35df8995e18a0Jean Chalard        } else if (isOnlyFirstCharCapitalized) {
28699b93d17d53c2d587c45373831b327f7851ec0a8Jean Chalard            sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
287ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        } else {
288ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            sb.append(wordInfo.mWord);
289ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
290e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        // Appending quotes is here to help people quote words. However, it's not helpful
291e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        // when they type words with quotes toward the end like "it's" or "didn't", where
292e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        // it's more likely the user missed the last character (or didn't type it yet).
293e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        final int quotesToAppend = trailingSingleQuotesCount
294e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard                - (-1 == wordInfo.mWord.indexOf(Constants.CODE_SINGLE_QUOTE) ? 0 : 1);
295e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        for (int i = quotesToAppend - 1; i >= 0; --i) {
296240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka            sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
297ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
2984bffac6db9e5800ecc58ba12d86b98a184779ebaKeisuke Kuroyanagi        return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKindAndFlags,
29924aad5a4d545e743fe43953c1a9d8141c022d355Jean Chalard                wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
3005b5ed3d6092ea539d8cfebd786c63ec0c784040bJean Chalard                wordInfo.mAutoCommitFirstWordConfidence);
301ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard    }
302923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
303