/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.woontua.inputmethod.latin; import com.woontua.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; public class AutoCorrection { private static final boolean DBG = LatinImeLogger.sDBG; private static final String TAG = AutoCorrection.class.getSimpleName(); private AutoCorrection() { // Purely static class: can't instantiate. } public static CharSequence computeAutoCorrectionWord( final ConcurrentHashMap<String, Dictionary> dictionaries, final WordComposer wordComposer, final ArrayList<SuggestedWordInfo> suggestions, final CharSequence consideredWord, final float autoCorrectionThreshold, final CharSequence whitelistedWord) { if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) { return whitelistedWord; } else if (hasAutoCorrectionForConsideredWord( dictionaries, wordComposer, suggestions, consideredWord)) { return consideredWord; } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, consideredWord, autoCorrectionThreshold)) { return suggestions.get(0).mWord; } return null; } public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) { if (TextUtils.isEmpty(word)) { return false; } final CharSequence lowerCasedWord = word.toString().toLowerCase(); for (final String key : dictionaries.keySet()) { if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue; final Dictionary dictionary = dictionaries.get(key); // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow // managing to get null in here. Presumably the language is changing to a language with // no main dictionary and the monkey manages to type a whole word before the thread // that reads the dictionary is started or something? // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and // would be immutable once it's finished initializing, but concretely a null test is // probably good enough for the time being. if (null == dictionary) continue; if (dictionary.isValidWord(word) || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) { return true; } } return false; } public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries, CharSequence word) { if (TextUtils.isEmpty(word)) { return Dictionary.NOT_A_PROBABILITY; } int maxFreq = -1; for (final String key : dictionaries.keySet()) { if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue; final Dictionary dictionary = dictionaries.get(key); if (null == dictionary) continue; final int tempFreq = dictionary.getFrequency(word); if (tempFreq >= maxFreq) { maxFreq = tempFreq; } } return maxFreq; } public static boolean allowsToBeAutoCorrected( final ConcurrentHashMap<String, Dictionary> dictionaries, final CharSequence word, final boolean ignoreCase) { final WhitelistDictionary whitelistDictionary = (WhitelistDictionary)dictionaries.get(Suggest.DICT_KEY_WHITELIST); // If "word" is in the whitelist dictionary, it should not be auto corrected. if (whitelistDictionary != null && whitelistDictionary.shouldForciblyAutoCorrectFrom(word)) { return true; } return !isValidWord(dictionaries, word, ignoreCase); } private static boolean hasAutoCorrectionForWhitelistedWord(CharSequence whiteListedWord) { return whiteListedWord != null; } private static boolean hasAutoCorrectionForConsideredWord( final ConcurrentHashMap<String, Dictionary> dictionaries, final WordComposer wordComposer, final ArrayList<SuggestedWordInfo> suggestions, final CharSequence consideredWord) { if (TextUtils.isEmpty(consideredWord)) return false; return wordComposer.size() > 1 && suggestions.size() > 0 && !allowsToBeAutoCorrected(dictionaries, consideredWord, false); } private static boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer, ArrayList<SuggestedWordInfo> suggestions, CharSequence consideredWord, float autoCorrectionThreshold) { if (wordComposer.size() > 1 && suggestions.size() > 0) { final SuggestedWordInfo autoCorrectionSuggestion = suggestions.get(0); //final int autoCorrectionSuggestionScore = sortedScores[0]; final int autoCorrectionSuggestionScore = autoCorrectionSuggestion.mScore; // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. final float normalizedScore = BinaryDictionary.calcNormalizedScore( consideredWord.toString(), autoCorrectionSuggestion.mWord.toString(), autoCorrectionSuggestionScore); if (DBG) { Log.d(TAG, "Normalized " + consideredWord + "," + autoCorrectionSuggestion + "," + autoCorrectionSuggestionScore + ", " + normalizedScore + "(" + autoCorrectionThreshold + ")"); } if (normalizedScore >= autoCorrectionThreshold) { if (DBG) { Log.d(TAG, "Auto corrected by S-threshold."); } return true; } } return false; } }