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