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
21ed378c78a15757c7386d84c6cd7470d56ed00c76Dan Zivkovicimport static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION;
228844c35e7ed4a04cefaaeae09a74e35be5cae71bDan Zivkovicimport static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION;
23ed378c78a15757c7386d84c6cd7470d56ed00c76Dan Zivkovic
24487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanevimport com.android.inputmethod.keyboard.Keyboard;
25def4551c2a570e7f575b2e9303506d790c2f335fJean Chalardimport com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
269342484e8d573a40f470b6a593df31c602fa4076Ken Wakasaimport com.android.inputmethod.latin.common.Constants;
274beeb9253a06482299e0c67467531d30436a02fcJean Chalardimport com.android.inputmethod.latin.common.StringUtils;
282dae79b1966a7970c25c8b79beec1c95c13f6c87Tadashi G. Takaokaimport com.android.inputmethod.latin.define.DebugFlags;
29b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagiimport com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
30b03447e1af950888d901fccbd2cc3e3b4a11ef98Ken Wakasaimport com.android.inputmethod.latin.utils.AutoCorrectionUtils;
31e784148ae6872942434eaa55ca32b4c6442cc8e8Keisuke Kuroyanagiimport com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
32adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagiimport com.android.inputmethod.latin.utils.SuggestionResults;
33043f7841985916717f4fa821fe3e423daf3ff2f5Jean Chalard
34fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaokaimport java.util.ArrayList;
358fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalardimport java.util.HashMap;
36cba93f50c3d46ada773ec49435689dc3e2094385Jean Chalardimport java.util.Locale;
37fa086c90760bc2bedf0b74eacb0fed3bf7ebc2b7Tadashi G. Takaoka
38e752aab70dc15c993a65d7db8314a72bb9e0f8b2Jean Chalardimport javax.annotation.Nonnull;
39a7efe062087049609af801e95257e555d9394aefJean Chalard
40923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project/**
41e90b333017c68e888a5e3d351f07ea29036457d0Ken Wakasa * This class loads a dictionary and provides a list of suggestions for a given sequence of
42923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project * characters. This includes corrections and completions.
43923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project */
44a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class Suggest {
4582411d47ba7e8133ed2390c6920945e139a738cesatok    public static final String TAG = Suggest.class.getSimpleName();
46cdbbea735f590784791f0c1fe33a514c4e864836satok
47f035649cb612be8b80892c510bbc137a615719b4Tadashi G. Takaoka    // Session id for
48bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka    // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
49f1233b58c2d81b575c92339f146cfe0f73a992faKeisuke Kuroyanagi    // We are sharing the same ID between typing and gesture to save RAM footprint.
50b8d764772b174cbd37354ffd0009bda56f223dc4Jean Chalard    public static final int SESSION_ID_TYPING = 0;
51b8d764772b174cbd37354ffd0009bda56f223dc4Jean Chalard    public static final int SESSION_ID_GESTURE = 0;
52979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
53a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard    // Close to -2**31
54a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard    private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
55a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
562dae79b1966a7970c25c8b79beec1c95c13f6c87Tadashi G. Takaoka    private static final boolean DBG = DebugFlags.DEBUG_ENABLED;
57a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi    private final DictionaryFacilitator mDictionaryFacilitator;
58979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
598fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard    private static final int MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN = 12;
608fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard    private static final HashMap<String, Integer> sLanguageToMaximumAutoCorrectionWithSpaceLength =
618fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard            new HashMap<>();
628fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard    static {
638fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        // TODO: should we add Finnish here?
648fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        // TODO: This should not be hardcoded here but be written in the dictionary header
658fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        sLanguageToMaximumAutoCorrectionWithSpaceLength.put(Locale.GERMAN.getLanguage(),
668fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                MAXIMUM_AUTO_CORRECT_LENGTH_FOR_GERMAN);
678fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard    }
688fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard
690028ed3627ff4f37a62a80f3b2c857e373cd5090satok    private float mAutoCorrectionThreshold;
7056577461d63ad3618598ceddfb9a73b797917061Jean Chalard    private float mPlausibilityThreshold;
71979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
72a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi    public Suggest(final DictionaryFacilitator dictionaryFacilitator) {
73a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi        mDictionaryFacilitator = dictionaryFacilitator;
74a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi    }
75a1035be6d877cafda95b2761f9697474b79deeb8Keisuke Kuroyanagi
7656577461d63ad3618598ceddfb9a73b797917061Jean Chalard    /**
7756577461d63ad3618598ceddfb9a73b797917061Jean Chalard     * Set the normalized-score threshold for a suggestion to be considered strong enough that we
7856577461d63ad3618598ceddfb9a73b797917061Jean Chalard     * will auto-correct to this.
7956577461d63ad3618598ceddfb9a73b797917061Jean Chalard     * @param threshold the threshold
8056577461d63ad3618598ceddfb9a73b797917061Jean Chalard     */
81adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi    public void setAutoCorrectionThreshold(final float threshold) {
821b1f7f907f6c7d6e849c88ca06c3608bc84d7c5fTadashi G. Takaoka        mAutoCorrectionThreshold = threshold;
83b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda    }
84b1abda8d62d654e876c4f781a07d724922c736e4Mitsuhiro Shimoda
8556577461d63ad3618598ceddfb9a73b797917061Jean Chalard    /**
8656577461d63ad3618598ceddfb9a73b797917061Jean Chalard     * Set the normalized-score threshold for what we consider a "plausible" suggestion, in
8756577461d63ad3618598ceddfb9a73b797917061Jean Chalard     * the same dimension as the auto-correction threshold.
8856577461d63ad3618598ceddfb9a73b797917061Jean Chalard     * @param threshold the threshold
8956577461d63ad3618598ceddfb9a73b797917061Jean Chalard     */
9056577461d63ad3618598ceddfb9a73b797917061Jean Chalard    public void setPlausibilityThreshold(final float threshold) {
9156577461d63ad3618598ceddfb9a73b797917061Jean Chalard        mPlausibilityThreshold = threshold;
9256577461d63ad3618598ceddfb9a73b797917061Jean Chalard    }
9356577461d63ad3618598ceddfb9a73b797917061Jean Chalard
949666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    public interface OnGetSuggestedWordsCallback {
959666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada        public void onGetSuggestedWords(final SuggestedWords suggestedWords);
969666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    }
979666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada
989666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    public void getSuggestedWords(final WordComposer wordComposer,
99487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            final NgramContext ngramContext, final Keyboard keyboard,
100b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi            final SettingsValuesForSuggestion settingsValuesForSuggestion,
101b8d764772b174cbd37354ffd0009bda56f223dc4Jean Chalard            final boolean isCorrectionEnabled, final int inputStyle, final int sequenceNumber,
102487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            final OnGetSuggestedWordsCallback callback) {
103d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        if (wordComposer.isBatchMode()) {
104487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            getSuggestedWordsForBatchInput(wordComposer, ngramContext, keyboard,
105487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev                    settingsValuesForSuggestion, inputStyle, sequenceNumber, callback);
106d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        } else {
107487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            getSuggestedWordsForNonBatchInput(wordComposer, ngramContext, keyboard,
108b8d764772b174cbd37354ffd0009bda56f223dc4Jean Chalard                    settingsValuesForSuggestion, inputStyle, isCorrectionEnabled,
109487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev                    sequenceNumber, callback);
110d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        }
111d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
112d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
113e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard    private static ArrayList<SuggestedWordInfo> getTransformedSuggestedWordInfoList(
114e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            final WordComposer wordComposer, final SuggestionResults results,
115576c96af95d7f1df869224ada78933d968e9a9c3Jean Chalard            final int trailingSingleQuotesCount, final Locale defaultLocale) {
116e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final boolean shouldMakeSuggestionsAllUpperCase = wordComposer.isAllUpperCase()
117e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                && !wordComposer.isResumed();
118e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final boolean isOnlyFirstCharCapitalized =
119e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                wordComposer.isOrWillBeOnlyFirstCharCapitalized();
120e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard
121e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final ArrayList<SuggestedWordInfo> suggestionsContainer = new ArrayList<>(results);
122e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final int suggestionsCount = suggestionsContainer.size();
123e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        if (isOnlyFirstCharCapitalized || shouldMakeSuggestionsAllUpperCase
124e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                || 0 != trailingSingleQuotesCount) {
125e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            for (int i = 0; i < suggestionsCount; ++i) {
126e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
127576c96af95d7f1df869224ada78933d968e9a9c3Jean Chalard                final Locale wordLocale = wordInfo.mSourceDict.mLocale;
128e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
129576c96af95d7f1df869224ada78933d968e9a9c3Jean Chalard                        wordInfo, null == wordLocale ? defaultLocale : wordLocale,
130576c96af95d7f1df869224ada78933d968e9a9c3Jean Chalard                        shouldMakeSuggestionsAllUpperCase, isOnlyFirstCharCapitalized,
131576c96af95d7f1df869224ada78933d968e9a9c3Jean Chalard                        trailingSingleQuotesCount);
132e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                suggestionsContainer.set(i, transformedWordInfo);
133e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            }
134e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        }
135e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        return suggestionsContainer;
136e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard    }
137e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard
138e752aab70dc15c993a65d7db8314a72bb9e0f8b2Jean Chalard    private static SuggestedWordInfo getWhitelistedWordInfoOrNull(
139e752aab70dc15c993a65d7db8314a72bb9e0f8b2Jean Chalard            @Nonnull final ArrayList<SuggestedWordInfo> suggestions) {
140e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        if (suggestions.isEmpty()) {
141e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            return null;
142e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        }
143e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final SuggestedWordInfo firstSuggestedWordInfo = suggestions.get(0);
144e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
145e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard            return null;
146e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        }
147e752aab70dc15c993a65d7db8314a72bb9e0f8b2Jean Chalard        return firstSuggestedWordInfo;
148e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard    }
149e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard
150b8d764772b174cbd37354ffd0009bda56f223dc4Jean Chalard    // Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
1519666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // and calls the callback function with the suggestions.
152b8d764772b174cbd37354ffd0009bda56f223dc4Jean Chalard    private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
153487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            final NgramContext ngramContext, final Keyboard keyboard,
1548380f921f7edaeea2033a1e967a14941400fe246Jean Chalard            final SettingsValuesForSuggestion settingsValuesForSuggestion,
1558380f921f7edaeea2033a1e967a14941400fe246Jean Chalard            final int inputStyleIfNotPrediction, final boolean isCorrectionEnabled,
156487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
157a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard        final String typedWordString = wordComposer.getTypedWord();
158a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard        final int trailingSingleQuotesCount =
159a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                StringUtils.getTrailingSingleQuotesCount(typedWordString);
16010abf10c1fd3782389cbec1aec7b91855a7b5154Jean Chalard        final String consideredWord = trailingSingleQuotesCount > 0
161a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                ? typedWordString.substring(0, typedWordString.length() - trailingSingleQuotesCount)
162a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                : typedWordString;
163979f8690967ff5409fe18f5085858ccdb8e0ccf1satok
164adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi        final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
165487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev                wordComposer.getComposedDataSnapshot(), ngramContext, keyboard,
166487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev                settingsValuesForSuggestion, SESSION_ID_TYPING, inputStyleIfNotPrediction);
167107fb4c476779df16be23e245547253978c197acDan Zivkovic        final Locale locale = mDictionaryFacilitator.getLocale();
1681ec3f158d2532d71c8660f02710b342c7a16f330Jean Chalard        final ArrayList<SuggestedWordInfo> suggestionsContainer =
169e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
1704e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic                        trailingSingleQuotesCount, locale);
1714e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic
1724e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic        boolean foundInDictionary = false;
1734e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic        Dictionary sourceDictionaryOfRemovedWord = null;
17456577461d63ad3618598ceddfb9a73b797917061Jean Chalard        for (final SuggestedWordInfo info : suggestionsContainer) {
17556577461d63ad3618598ceddfb9a73b797917061Jean Chalard            // Search for the best dictionary, defined as the first one with the highest match
17656577461d63ad3618598ceddfb9a73b797917061Jean Chalard            // quality we can find.
1774e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic            if (!foundInDictionary && typedWordString.equals(info.mWord)) {
1784e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic                // Use this source if the old match had lower quality than this match
1794e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic                sourceDictionaryOfRemovedWord = info.mSourceDict;
1804e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic                foundInDictionary = true;
1814e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic                break;
18256577461d63ad3618598ceddfb9a73b797917061Jean Chalard            }
18356577461d63ad3618598ceddfb9a73b797917061Jean Chalard        }
18456577461d63ad3618598ceddfb9a73b797917061Jean Chalard
1855551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic        final int firstOcurrenceOfTypedWordInSuggestions =
1865551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic                SuggestedWordInfo.removeDups(typedWordString, suggestionsContainer);
1871ec3f158d2532d71c8660f02710b342c7a16f330Jean Chalard
188e752aab70dc15c993a65d7db8314a72bb9e0f8b2Jean Chalard        final SuggestedWordInfo whitelistedWordInfo =
189e752aab70dc15c993a65d7db8314a72bb9e0f8b2Jean Chalard                getWhitelistedWordInfoOrNull(suggestionsContainer);
1904e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic        final String whitelistedWord = whitelistedWordInfo == null
1914e1f0f1bb03551efde3bc6b0a8e60465c088cda2Dan Zivkovic                ? null : whitelistedWordInfo.mWord;
192e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard        final boolean resultsArePredictions = !wordComposer.isComposingWord();
1937b258e512dc2a8c821eb9f435e5719b8a967b441Jean Chalard
194ed378c78a15757c7386d84c6cd7470d56ed00c76Dan Zivkovic        // We allow auto-correction if whitelisting is not required or the word is whitelisted,
195ed378c78a15757c7386d84c6cd7470d56ed00c76Dan Zivkovic        // or if the word had more than one char and was not suggested.
196ed378c78a15757c7386d84c6cd7470d56ed00c76Dan Zivkovic        final boolean allowsToBeAutoCorrected =
197ed378c78a15757c7386d84c6cd7470d56ed00c76Dan Zivkovic                (SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION || whitelistedWord != null)
198ed378c78a15757c7386d84c6cd7470d56ed00c76Dan Zivkovic                || (consideredWord.length() > 1 && (sourceDictionaryOfRemovedWord == null));
199caed149b67be378adf49f3db16a2cfbb8dd15d84Jean Chalard
20067af2a24157ead953607bdfd585fba3a7e6bf50cJean Chalard        final boolean hasAutoCorrection;
20167527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard        // If correction is not enabled, we never auto-correct. This is for example for when
20267527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard        // the setting "Auto-correction" is "off": we still suggest, but we don't auto-correct.
20367527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard        if (!isCorrectionEnabled
20467527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // If the word does not allow to be auto-corrected, then we don't auto-correct.
20567527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                || !allowsToBeAutoCorrected
20667527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // If we are doing prediction, then we never auto-correct of course
20767527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                || resultsArePredictions
20867527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // If we don't have suggestion results, we can't evaluate the first suggestion
20967527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // for auto-correction
21067527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                || suggestionResults.isEmpty()
21167527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // If the word has digits, we never auto-correct because it's likely the word
21267527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // was type with a lot of care
21367527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                || wordComposer.hasDigits()
21467527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // If the word is mostly caps, we never auto-correct because this is almost
21567527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // certainly intentional (and careful input)
21667527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                || wordComposer.isMostlyCaps()
21767527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // We never auto-correct when suggestions are resumed because it would be unexpected
21867527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                || wordComposer.isResumed()
21967527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // If we don't have a main dictionary, we never want to auto-correct. The reason
22067527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // for this is, the user may have a contact whose name happens to match a valid
22167527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // word in their language, and it will unexpectedly auto-correct. For example, if
22267527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // the user types in English with no dictionary and has a "Will" in their contact
22367527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // list, "will" would always auto-correct to "Will" which is unwanted. Hence, no
22467527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // main dict => no auto-correct. Also, it would probably get obnoxious quickly.
22567527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // TODO: now that we have personalization, we may want to re-evaluate this decision
2268cd53266229895a3e0c6618e3765d57fc5d0b392Jean Chalard                || !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
22767527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // If the first suggestion is a shortcut we never auto-correct to it, regardless
22867527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // of how strong it is (whitelist entries are not KIND_SHORTCUT but KIND_WHITELIST).
22967527f847e4c4cc029accc83dad1a8b83bcff5feJean Chalard                // TODO: we may want to have shortcut-only entries auto-correct in the future.
230e5a8615de706e47ec0a25022aed4df44f4d4b155Jean Chalard                || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
231ea578f6b1dbcf04ffcc9c673f72a38ed2cfecdfcJean Chalard            hasAutoCorrection = false;
23294b20c90d86aa042c2f361597665045271956decJean Chalard        } else {
2338fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard            final SuggestedWordInfo firstSuggestion = suggestionResults.first();
2345551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic            if (suggestionResults.mFirstSuggestionExceedsConfidenceThreshold
2355551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic                    && firstOcurrenceOfTypedWordInSuggestions != 0) {
236b00c054125d9f2aa31c2147920cc52cbf2a45cccMohammadinamul Sheik                hasAutoCorrection = true;
237b00c054125d9f2aa31c2147920cc52cbf2a45cccMohammadinamul Sheik            } else if (!AutoCorrectionUtils.suggestionExceedsThreshold(
2388fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                    firstSuggestion, consideredWord, mAutoCorrectionThreshold)) {
2398fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                // Score is too low for autocorrect
2408fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                hasAutoCorrection = false;
2418fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard            } else {
2428fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                // We have a high score, so we need to check if this suggestion is in the correct
2438fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                // form to allow auto-correcting to it in this language. For details of how this
2448fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                // is determined, see #isAllowedByAutoCorrectionWithSpaceFilter.
2458fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                // TODO: this should not have its own logic here but be handled by the dictionary.
2468fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                hasAutoCorrection = isAllowedByAutoCorrectionWithSpaceFilter(firstSuggestion);
2478fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard            }
24894b20c90d86aa042c2f361597665045271956decJean Chalard        }
2499f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
250a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard        final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(typedWordString,
251ab5912959435c1901e268bc9766090e604f3523dMohammadinamul Sheik                "" /* prevWordsContext */, SuggestedWordInfo.MAX_SCORE,
252ab5912959435c1901e268bc9766090e604f3523dMohammadinamul Sheik                SuggestedWordInfo.KIND_TYPED,
253a7efe062087049609af801e95257e555d9394aefJean Chalard                null == sourceDictionaryOfRemovedWord ? Dictionary.DICTIONARY_USER_TYPED
254a7efe062087049609af801e95257e555d9394aefJean Chalard                        : sourceDictionaryOfRemovedWord,
255a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
256a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
257a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard        if (!TextUtils.isEmpty(typedWordString)) {
258a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard            suggestionsContainer.add(0, typedWordInfo);
25928eeb35d149468514a65379e9d0d1672cf26981eJean Chalard        }
2609f67e12a0e3f77985fb8bafe0db4c00e32317b9asatok
261def4551c2a570e7f575b2e9303506d790c2f335fJean Chalard        final ArrayList<SuggestedWordInfo> suggestionsList;
262bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard        if (DBG && !suggestionsContainer.isEmpty()) {
263a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard            suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWordString,
264a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                    suggestionsContainer);
265ed9986824e1339855376771ad29fae4de921a029Jean Chalard        } else {
266bed514bd902d9736edcbfe03d37d8cced2bb03a3Jean Chalard            suggestionsList = suggestionsContainer;
2678553b5ec315660ab53dd9234e64e1e39ea09ec0fJean Chalard        }
268ed9986824e1339855376771ad29fae4de921a029Jean Chalard
269f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka        final int inputStyle;
270f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka        if (resultsArePredictions) {
271f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka            inputStyle = suggestionResults.mIsBeginningOfSentence
272f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka                    ? SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION
273f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka                    : SuggestedWords.INPUT_STYLE_PREDICTION;
274f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka        } else {
275f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka            inputStyle = inputStyleIfNotPrediction;
276f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka        }
2775551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic
2785551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic        final boolean isTypedWordValid = firstOcurrenceOfTypedWordInSuggestions > -1
2795551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic                || (!resultsArePredictions && !allowsToBeAutoCorrected);
280e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard        callback.onGetSuggestedWords(new SuggestedWords(suggestionsList,
281a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                suggestionResults.mRawSuggestions, typedWordInfo,
2825551302d275e3f54da9d86bcea633556ad12db8eDan Zivkovic                isTypedWordValid,
283e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard                hasAutoCorrection /* willAutoCorrect */,
2848380f921f7edaeea2033a1e967a14941400fe246Jean Chalard                false /* isObsoleteSuggestions */, inputStyle, sequenceNumber));
285923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project    }
286923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project
2879666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // Retrieves suggestions for the batch input
2889666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    // and calls the callback function with the suggestions.
2899666a228153bb2269da8983762bdd47e448f2cecYuichiro Hanada    private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
290487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            final NgramContext ngramContext, final Keyboard keyboard,
291b8a9479b57007edb5cb12c628797f89a8164f596Keisuke Kuroyanagi            final SettingsValuesForSuggestion settingsValuesForSuggestion,
292b8d764772b174cbd37354ffd0009bda56f223dc4Jean Chalard            final int inputStyle, final int sequenceNumber,
293487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev            final OnGetSuggestedWordsCallback callback) {
294adfb262797023c4ca57bb470e547f90c88f638caKeisuke Kuroyanagi        final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
295487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev                wordComposer.getComposedDataSnapshot(), ngramContext, keyboard,
296487e038ff329b6099ff5343fb2d7bdc60a6fd699Mario Tanev                settingsValuesForSuggestion, SESSION_ID_GESTURE, inputStyle);
297ea077349563d704dc3fdecf31a6f9f7f95ce4ad4Jean Chalard        // For transforming words that don't come from a dictionary, because it's our best bet
298107fb4c476779df16be23e245547253978c197acDan Zivkovic        final Locale locale = mDictionaryFacilitator.getLocale();
299d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka        final ArrayList<SuggestedWordInfo> suggestionsContainer =
300a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka                new ArrayList<>(suggestionResults);
301eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        final int suggestionsCount = suggestionsContainer.size();
3021eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
3031eba97d92fb5caa4f23425837b6680ccc2a23ae8Jean Chalard        final boolean isAllUpperCase = wordComposer.isAllUpperCase();
304eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        if (isFirstCharCapitalized || isAllUpperCase) {
305eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            for (int i = 0; i < suggestionsCount; ++i) {
306eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
307576c96af95d7f1df869224ada78933d968e9a9c3Jean Chalard                final Locale wordlocale = wordInfo.mSourceDict.mLocale;
308eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
309107fb4c476779df16be23e245547253978c197acDan Zivkovic                        wordInfo, null == wordlocale ? locale : wordlocale, isAllUpperCase,
310576c96af95d7f1df869224ada78933d968e9a9c3Jean Chalard                        isFirstCharCapitalized, 0 /* trailingSingleQuotesCount */);
311eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                suggestionsContainer.set(i, transformedWordInfo);
312eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang            }
313eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        }
314d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
3158844c35e7ed4a04cefaaeae09a74e35be5cae71bDan Zivkovic        if (SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION
3168844c35e7ed4a04cefaaeae09a74e35be5cae71bDan Zivkovic                && suggestionsContainer.size() > 1
3178844c35e7ed4a04cefaaeae09a74e35be5cae71bDan Zivkovic                && TextUtils.equals(suggestionsContainer.get(0).mWord,
3188844c35e7ed4a04cefaaeae09a74e35be5cae71bDan Zivkovic                   wordComposer.getRejectedBatchModeSuggestion())) {
319d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard            final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
320d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard            suggestionsContainer.add(1, rejected);
321d40f3f6bc1bcf07800fbee0468fe90d307ca28bbJean Chalard        }
32256577461d63ad3618598ceddfb9a73b797917061Jean Chalard        SuggestedWordInfo.removeDups(null /* typedWord */, suggestionsContainer);
323a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
324a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        // For some reason some suggestions with MIN_VALUE are making their way here.
325f116f9103377866c3a1e5d2b03907bbfead2970bAdrian Velicu        // TODO: Find a more robust way to detect distracters.
326a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
327a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard            if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
328a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard                suggestionsContainer.remove(i);
329a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard            }
330a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard        }
331a5a2f3e3c77ebf2e1bb74b08c8587c15b9711ac8Jean Chalard
332eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // In the batch input mode, the most relevant suggested word should act as a "typed word"
333eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang        // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
334f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka        // Note that because this method is never used to get predictions, there is no need to
335f4c7eb478f67874e81cce786bf91ab112da316e1Tadashi G. Takaoka        // modify inputType such in getSuggestedWordsForNonBatchInput.
336a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard        final SuggestedWordInfo pseudoTypedWordInfo = suggestionsContainer.isEmpty() ? null
337a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                : suggestionsContainer.get(0);
338c3e211bd6be2f35cb7276319f6f4dfa29c315603Jean Chalard
339e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard        callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer,
340e83e79cb055fbfe5171fb79a2224e7d9e2cda4d2Jean Chalard                suggestionResults.mRawSuggestions,
341a2cb2f36a6b51f73602bd7e917c418657da0c973Jean Chalard                pseudoTypedWordInfo,
342d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                true /* typedWordValid */,
343eea34598bf63f670f47d7b3f37b6436921e5fe02Tom Ouyang                false /* willAutoCorrect */,
344d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka                false /* isObsoleteSuggestions */,
345b8d764772b174cbd37354ffd0009bda56f223dc4Jean Chalard                inputStyle, sequenceNumber));
346d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka    }
347d82dcdc9246b340c4b355e34efc6079f3278efa6Tadashi G. Takaoka
3480d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
3497e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final String typedWord, final ArrayList<SuggestedWordInfo> suggestions) {
3507e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        final SuggestedWordInfo typedWordInfo = suggestions.get(0);
3517e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        typedWordInfo.setDebugString("+");
3520d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        final int suggestionsSize = suggestions.size();
353a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(suggestionsSize);
3547e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        suggestionsList.add(typedWordInfo);
3550d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // Note: i here is the index in mScores[], but the index in mSuggestions is one more
3560d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        // than i because we added the typed word to mSuggestions without touching mScores.
3577e518d8b8358c96b94b900f0917cdc5fd8190ce1satok        for (int i = 0; i < suggestionsSize - 1; ++i) {
3587e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            final SuggestedWordInfo cur = suggestions.get(i + 1);
359e784148ae6872942434eaa55ca32b4c6442cc8e8Keisuke Kuroyanagi            final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
360db1939dbaa1de59eaf5693e2c89b02b323e9aac8satok                    typedWord, cur.toString(), cur.mScore);
3610d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            final String scoreInfoString;
3620d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            if (normalizedScore > 0) {
36394027c7201a376107a35ec78cd21db1905662601Tadashi G. Takaoka                scoreInfoString = String.format(
3642fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                        Locale.ROOT, "%d (%4.2f), %s", cur.mScore, normalizedScore,
3652fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                        cur.mSourceDict.mDictType);
3660d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            } else {
3677e518d8b8358c96b94b900f0917cdc5fd8190ce1satok                scoreInfoString = Integer.toString(cur.mScore);
3680d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard            }
3697e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            cur.setDebugString(scoreInfoString);
3707e518d8b8358c96b94b900f0917cdc5fd8190ce1satok            suggestionsList.add(cur);
3710d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        }
3720d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard        return suggestionsList;
3730d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard    }
3740d0f182959600d83c376e6b844337ea45e5ddbbfJean Chalard
3758fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard    /**
3768fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * Computes whether this suggestion should be blocked or not in this language
3778fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     *
3788fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * This function implements a filter that avoids auto-correcting to suggestions that contain
3798fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * spaces that are above a certain language-dependent character limit. In languages like German
3808fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * where it's possible to concatenate many words, it often happens our dictionary does not
3818fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * have the longer words. In this case, we offer a lot of unhelpful suggestions that contain
3828fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * one or several spaces. Ideally we should understand what the user wants and display useful
3838fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * suggestions by improving the dictionary and possibly having some specific logic. Until
3848fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * that's possible we should avoid displaying unhelpful suggestions. But it's hard to tell
3858fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * whether a suggestion is useful or not. So at least for the time being we block
3868fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * auto-correction when the suggestion is long and contains a space, which should avoid the
3878fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * worst damage.
3888fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * This function is implementing that filter. If the language enforces no such limit, then it
3898fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * always returns true. If the suggestion contains no space, it also returns true. Otherwise,
3908fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * it checks the length against the language-specific limit.
3918fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     *
3928fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * @param info the suggestion info
3938fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     * @return whether it's fine to auto-correct to this.
3948fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard     */
3955f00fe09e9a611b647592188316e5999465df4d3Tadashi G. Takaoka    private static boolean isAllowedByAutoCorrectionWithSpaceFilter(final SuggestedWordInfo info) {
3968fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        final Locale locale = info.mSourceDict.mLocale;
3978fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        if (null == locale) {
3988fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard            return true;
3998fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        }
4008fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        final Integer maximumLengthForThisLanguage =
4018fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                sLanguageToMaximumAutoCorrectionWithSpaceLength.get(locale.getLanguage());
4028fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        if (null == maximumLengthForThisLanguage) {
4038fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard            // This language does not enforce a maximum length to auto-correction
4048fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard            return true;
4058fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        }
4068fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard        return info.mWord.length() <= maximumLengthForThisLanguage
4078fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard                || -1 == info.mWord.indexOf(Constants.CODE_SPACE);
4088fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard    }
4098fc872762f56ac22a7146f61d3a8495c6652eadfJean Chalard
410e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard    /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
411ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
412367c199de16f7ce8e608bdf38bf35df8995e18a0Jean Chalard            final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) {
4139011b89f4ea0d73f1ad78b2dd0a6557b950fddd9Jean Chalard        final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
414ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        if (isAllUpperCase) {
415bc464e2952e102219f0b977fc1e9140ad5bd03e4Tadashi G. Takaoka            sb.append(wordInfo.mWord.toUpperCase(locale));
416367c199de16f7ce8e608bdf38bf35df8995e18a0Jean Chalard        } else if (isOnlyFirstCharCapitalized) {
41799b93d17d53c2d587c45373831b327f7851ec0a8Jean Chalard            sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
418ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        } else {
419ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard            sb.append(wordInfo.mWord);
420ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
421e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        // Appending quotes is here to help people quote words. However, it's not helpful
422e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        // when they type words with quotes toward the end like "it's" or "didn't", where
423e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        // it's more likely the user missed the last character (or didn't type it yet).
424e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        final int quotesToAppend = trailingSingleQuotesCount
425e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard                - (-1 == wordInfo.mWord.indexOf(Constants.CODE_SINGLE_QUOTE) ? 0 : 1);
426e8f717943f7063444cd1c777e8dd03dc738f3c4aJean Chalard        for (int i = quotesToAppend - 1; i >= 0; --i) {
427240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka            sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
428ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard        }
429ab5912959435c1901e268bc9766090e604f3523dMohammadinamul Sheik        return new SuggestedWordInfo(sb.toString(), wordInfo.mPrevWordsContext,
430ab5912959435c1901e268bc9766090e604f3523dMohammadinamul Sheik                wordInfo.mScore, wordInfo.mKindAndFlags,
43124aad5a4d545e743fe43953c1a9d8141c022d355Jean Chalard                wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
4325b5ed3d6092ea539d8cfebd786c63ec0c784040bJean Chalard                wordInfo.mAutoCommitFirstWordConfidence);
433ec8b27fe49bd0a149cf7dcd36d1b0d966b03a3b5Jean Chalard    }
434923bf41f853a544fd0d71fbf7dc90359ec35981The Android Open Source Project}
435