diff options
Diffstat (limited to 'java/src')
9 files changed, 271 insertions, 230 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 6b4de184f..b09a27540 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -73,6 +73,8 @@ public class Key { private static final int LABEL_FLAGS_PRESERVE_CASE = 0x8000; private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x10000; private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x20000; + private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000; + private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000; /** Icon to display instead of a label. Icon takes precedence over a label */ private final int mIconId; @@ -240,7 +242,8 @@ public class Key { mDisabledIconId = style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconDisabled, KeyboardIconsSet.ICON_UNDEFINED); - mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags); + mLabelFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyLabelFlags) + | row.getDefaultKeyLabelFlags(); final boolean preserveCase = (mLabelFlags & LABEL_FLAGS_PRESERVE_CASE) != 0; int actionFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyActionFlags); String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys); @@ -265,8 +268,13 @@ public class Key { } mMoreKeysColumnAndFlags = moreKeysColumn; - final String[] additionalMoreKeys = style.getStringArray( - keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys); + final String[] additionalMoreKeys; + if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) { + additionalMoreKeys = null; + } else { + additionalMoreKeys = style.getStringArray( + keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys); + } moreKeys = KeySpecParser.insertAddtionalMoreKeys(moreKeys, additionalMoreKeys); if (moreKeys != null) { actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; @@ -284,8 +292,12 @@ public class Key { mLabel = adjustCaseOfStringForKeyboardId(style.getString( keyAttr, R.styleable.Keyboard_Key_keyLabel), preserveCase, params.mId); } - mHintLabel = adjustCaseOfStringForKeyboardId(style.getString( - keyAttr, R.styleable.Keyboard_Key_keyHintLabel), preserveCase, params.mId); + if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { + mHintLabel = null; + } else { + mHintLabel = adjustCaseOfStringForKeyboardId(style.getString( + keyAttr, R.styleable.Keyboard_Key_keyHintLabel), preserveCase, params.mId); + } String outputText = adjustCaseOfStringForKeyboardId(style.getString( keyAttr, R.styleable.Keyboard_Key_keyOutputText), preserveCase, params.mId); final int code = style.getInt( diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index 689e322ce..53467122a 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -505,6 +505,8 @@ public class Keyboard { private float mDefaultKeyWidth; /** Default height of a key in this row. */ public final int mRowHeight; + /** Default keyLabelFlags in this row. */ + private int mDefaultKeyLabelFlags; private final int mCurrentY; // Will be updated by {@link Key}'s constructor. @@ -525,6 +527,7 @@ public class Keyboard { params.mBaseWidth, params.mDefaultKeyWidth); keyAttr.recycle(); + mDefaultKeyLabelFlags = 0; mCurrentY = y; mCurrentX = 0.0f; } @@ -537,6 +540,14 @@ public class Keyboard { mDefaultKeyWidth = defaultKeyWidth; } + public int getDefaultKeyLabelFlags() { + return mDefaultKeyLabelFlags; + } + + public void setDefaultKeyLabelFlags(int keyLabelFlags) { + mDefaultKeyLabelFlags = keyLabelFlags; + } + public void setXPos(float keyXPos) { mCurrentX = keyXPos; } @@ -927,6 +938,7 @@ public class Keyboard { R.styleable.Keyboard_Key); int keyboardLayout = 0; float savedDefaultKeyWidth = 0; + int savedDefaultKeyLabelFlags = 0; try { XmlParseUtils.checkAttributeExists(keyboardAttr, R.styleable.Keyboard_Include_keyboardLayout, "keyboardLayout", @@ -935,6 +947,7 @@ public class Keyboard { R.styleable.Keyboard_Include_keyboardLayout, 0); if (row != null) { savedDefaultKeyWidth = row.getDefaultKeyWidth(); + savedDefaultKeyLabelFlags = row.getDefaultKeyLabelFlags(); if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyXPos)) { // Override current x coordinate. row.setXPos(row.getKeyX(keyAttr)); @@ -943,6 +956,12 @@ public class Keyboard { // Override default key width. row.setDefaultKeyWidth(row.getKeyWidth(keyAttr)); } + if (keyAttr.hasValue(R.styleable.Keyboard_Key_keyLabelFlags)) { + // Override default key label flags. + row.setDefaultKeyLabelFlags( + keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0) + | savedDefaultKeyLabelFlags); + } } } finally { keyboardAttr.recycle(); @@ -959,8 +978,9 @@ public class Keyboard { parseMerge(parserForInclude, row, skip); } finally { if (row != null) { - // Restore default key width. + // Restore default key width and key label flags. row.setDefaultKeyWidth(savedDefaultKeyWidth); + row.setDefaultKeyLabelFlags(savedDefaultKeyLabelFlags); } parserForInclude.close(); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java index 5ac6d03a8..680ff0d25 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java @@ -379,6 +379,8 @@ public class KeyboardSet { } } + // TODO: Should be removed. This is no longer required if {@link InputMethodSubtype} is + // supported. public static String parseKeyboardLocale(Resources res, int resId) throws XmlPullParserException, IOException { final XmlPullParser parser = res.getXml(resId); diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java index 1cbdbd650..9c5ccc76b 100644 --- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java +++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java @@ -16,9 +16,7 @@ package com.android.inputmethod.latin; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; import android.media.AudioManager; import android.view.HapticFeedbackConstants; import android.view.View; @@ -32,7 +30,7 @@ import com.android.inputmethod.keyboard.Keyboard; * It offers a consistent and simple interface that allows LatinIME to forget about the * complexity of settings and the like. */ -public class AudioAndHapticFeedbackManager extends BroadcastReceiver { +public class AudioAndHapticFeedbackManager { final private SettingsValues mSettingsValues; final private AudioManager mAudioManager; final private VibratorCompatWrapper mVibrator; @@ -100,13 +98,7 @@ public class AudioAndHapticFeedbackManager extends BroadcastReceiver { } } - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - // The following test is supposedly useless since we only listen for the ringer event. - // Still, it's a good safety measure. - if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - mSoundOn = reevaluateIfSoundIsOn(); - } + public void onRingerModeChanged() { + mSoundOn = reevaluateIfSoundIsOn(); } } diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index 425b5c3f3..9c35f8f6f 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -25,36 +25,25 @@ import java.util.Map; public class AutoCorrection { private static final boolean DBG = LatinImeLogger.sDBG; private static final String TAG = AutoCorrection.class.getSimpleName(); - private CharSequence mAutoCorrectionWord; - private double mNormalizedScore; - public void init() { - mAutoCorrectionWord = null; - mNormalizedScore = Integer.MIN_VALUE; + private AutoCorrection() { + // Purely static class: can't instantiate. } - public boolean hasAutoCorrection() { - return null != mAutoCorrectionWord; - } - - public double getNormalizedScore() { - return mNormalizedScore; - } - - public CharSequence updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries, + public static CharSequence computeAutoCorrectionWord(Map<String, Dictionary> dictionaries, WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores, - CharSequence typedWord, double autoCorrectionThreshold, int correctionMode, + CharSequence consideredWord, double autoCorrectionThreshold, CharSequence whitelistedWord) { if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) { - mAutoCorrectionWord = whitelistedWord; - } else if (hasAutoCorrectionForTypedWord( - dictionaries, wordComposer, suggestions, typedWord, correctionMode)) { - mAutoCorrectionWord = typedWord; - } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode, - sortedScores, typedWord, autoCorrectionThreshold)) { - mAutoCorrectionWord = suggestions.get(0); + return whitelistedWord; + } else if (hasAutoCorrectionForConsideredWord( + dictionaries, wordComposer, suggestions, consideredWord)) { + return consideredWord; + } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, + sortedScores, consideredWord, autoCorrectionThreshold)) { + return suggestions.get(0); } - return mAutoCorrectionWord; + return null; } public static boolean isValidWord( @@ -98,35 +87,31 @@ public class AutoCorrection { return whiteListedWord != null; } - private static boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries, - WordComposer wordComposer, ArrayList<CharSequence> suggestions, CharSequence typedWord, - int correctionMode) { - if (TextUtils.isEmpty(typedWord)) return false; + private static boolean hasAutoCorrectionForConsideredWord(Map<String, Dictionary> dictionaries, + WordComposer wordComposer, ArrayList<CharSequence> suggestions, + CharSequence consideredWord) { + if (TextUtils.isEmpty(consideredWord)) return false; return wordComposer.size() > 1 && suggestions.size() > 0 - && !allowsToBeAutoCorrected(dictionaries, typedWord, false) - && (correctionMode == Suggest.CORRECTION_FULL - || correctionMode == Suggest.CORRECTION_FULL_BIGRAM); + && !allowsToBeAutoCorrected(dictionaries, consideredWord, false); } - private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer, - ArrayList<CharSequence> suggestions, int correctionMode, int[] sortedScores, - CharSequence typedWord, double autoCorrectionThreshold) { - if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL - || correctionMode == Suggest.CORRECTION_FULL_BIGRAM) - && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) { + private static boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer, + ArrayList<CharSequence> suggestions, int[] sortedScores, + CharSequence consideredWord, double autoCorrectionThreshold) { + if (wordComposer.size() > 1 && suggestions.size() > 0 && sortedScores.length > 0) { final CharSequence autoCorrectionSuggestion = suggestions.get(0); final int autoCorrectionSuggestionScore = sortedScores[0]; // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. - mNormalizedScore = BinaryDictionary.calcNormalizedScore( - typedWord.toString(), autoCorrectionSuggestion.toString(), + final double normalizedScore = BinaryDictionary.calcNormalizedScore( + consideredWord.toString(), autoCorrectionSuggestion.toString(), autoCorrectionSuggestionScore); if (DBG) { - Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionSuggestion + "," - + autoCorrectionSuggestionScore + ", " + mNormalizedScore + Log.d(TAG, "Normalized " + consideredWord + "," + autoCorrectionSuggestion + "," + + autoCorrectionSuggestionScore + ", " + normalizedScore + "(" + autoCorrectionThreshold + ")"); } - if (mNormalizedScore >= autoCorrectionThreshold) { + if (normalizedScore >= autoCorrectionThreshold) { if (DBG) { Log.d(TAG, "Auto corrected by S-threshold."); } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index ca38cdeec..d96b858eb 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -72,6 +72,7 @@ import com.android.inputmethod.latin.suggestions.SuggestionsView; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.List; import java.util.Locale; /** @@ -532,6 +533,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // Also receive installation and removal of a dictionary pack. final IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); registerReceiver(mReceiver, filter); mVoiceProxy = VoiceProxy.init(this, prefs, mHandler); @@ -547,19 +549,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); } - private void renewFeedbackReceiver() { - if (null != mFeedbackManager) unregisterReceiver(mFeedbackManager); - mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues); - final IntentFilter ringerModeFilter = new IntentFilter(); - ringerModeFilter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); - registerReceiver(mFeedbackManager, ringerModeFilter); - } - // Has to be package-visible for unit tests /* package */ void loadSettings() { if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mSettingsValues = new SettingsValues(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr()); - renewFeedbackReceiver(); + mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues); resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); } @@ -648,7 +642,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mSuggest = null; } unregisterReceiver(mReceiver); - unregisterReceiver(mFeedbackManager); unregisterReceiver(mDictionaryPackInstallReceiver); mVoiceProxy.destroy(); LatinImeLogger.commit(); @@ -987,8 +980,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar return; } + final List<CharSequence> applicationSuggestedWords = + SuggestedWords.Builder.getFromApplicationSpecifiedCompletions( + applicationSpecifiedCompletions); SuggestedWords.Builder builder = new SuggestedWords.Builder() - .setApplicationSpecifiedCompletions(applicationSpecifiedCompletions) + .addWords(applicationSuggestedWords, null) .setTypedWordValid(false) .setHasMinimalSuggestion(false); // When in fullscreen mode, show completions generated by the application @@ -1292,7 +1288,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } private void handleLanguageSwitchKey() { - final boolean includesOtherImes = !mSettingsValues.mIncludesOtherImesInLanguageSwitchList; + final boolean includesOtherImes = mSettingsValues.mIncludesOtherImesInLanguageSwitchList; final IBinder token = getWindow().getWindow().getAttributes().token; if (mShouldSwitchToLastSubtype) { final InputMethodSubtypeCompatWrapper lastSubtype = mImm.getLastInputMethodSubtype(); @@ -1824,70 +1820,36 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } else { prevWord = EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators); } + + final CharSequence typedWord = mWordComposer.getTypedWord(); // getSuggestedWordBuilder handles gracefully a null value of prevWord final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(mWordComposer, prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCorrectionMode); - boolean autoCorrectionAvailable = !mInputAttributes.mInputTypeNoAutoCorrect - && mSuggest.hasAutoCorrection(); - final CharSequence typedWord = mWordComposer.getTypedWord(); - // Here, we want to promote a whitelisted word if exists. - // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid" - // but still autocorrected from - in the case the whitelist only capitalizes the word. - // The whitelist should be case-insensitive, so it's not possible to be consistent with - // a boolean flag. Right now this is handled with a slight hack in - // WhitelistDictionary#shouldForciblyAutoCorrectFrom. - final int quotesCount = mWordComposer.trailingSingleQuotesCount(); - final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected( - mSuggest.getUnigramDictionaries(), - // If the typed string ends with a single quote, for dictionary lookup purposes - // we behave as if the single quote was not here. Here, we are looking up the - // typed string in the dictionary (to avoid autocorrecting from an existing - // word, so for consistency this lookup should be made WITHOUT the trailing - // single quote. - quotesCount > 0 - ? typedWord.subSequence(0, typedWord.length() - quotesCount) : typedWord, - preferCapitalization()); - if (mCorrectionMode == Suggest.CORRECTION_FULL - || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { - autoCorrectionAvailable |= (!allowsToBeAutoCorrected); - } - // Don't auto-correct words with multiple capital letter - autoCorrectionAvailable &= !mWordComposer.isMostlyCaps(); - // Basically, we update the suggestion strip only when suggestion count > 1. However, // there is an exception: We update the suggestion strip whenever typed word's length // is 1 or typed word is found in dictionary, regardless of suggestion count. Actually, // in most cases, suggestion count is 1 when typed word's length is 1, but we do always // need to clear the previous state when the user starts typing a word (i.e. typed word's // length == 1). - if (typedWord != null) { - if (builder.size() > 1 || typedWord.length() == 1 || (!allowsToBeAutoCorrected) - || mSuggestionsView.isShowingAddToDictionaryHint()) { - builder.setTypedWordValid(!allowsToBeAutoCorrected).setHasMinimalSuggestion( - autoCorrectionAvailable); - } else { - SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions(); - if (previousSuggestions == mSettingsValues.mSuggestPuncList) { - if (builder.size() == 0) { - return; - } - previousSuggestions = SuggestedWords.EMPTY; - } - builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions); + if (builder.size() > 1 || typedWord.length() == 1 || !builder.allowsToBeAutoCorrected() + || mSuggestionsView.isShowingAddToDictionaryHint()) { + showSuggestions(builder.build(), typedWord); + } else { + SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions(); + if (previousSuggestions == mSettingsValues.mSuggestPuncList) { + previousSuggestions = SuggestedWords.EMPTY; } + final SuggestedWords.Builder obsoleteSuggestionsBuilder = new SuggestedWords.Builder() + .addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions); + showSuggestions(obsoleteSuggestionsBuilder.build(), typedWord); } - if (Suggest.shouldBlockAutoCorrectionBySafetyNet(builder, mSuggest)) { - builder.setShouldBlockAutoCorrectionBySafetyNet(); - } - showSuggestions(builder.build(), typedWord); } public void showSuggestions(final SuggestedWords suggestedWords, final CharSequence typedWord) { final CharSequence autoCorrection; if (suggestedWords.size() > 0) { - if (!suggestedWords.mShouldBlockAutoCorrectionBySafetyNet - && suggestedWords.hasAutoCorrectionWord()) { + if (suggestedWords.hasAutoCorrectionWord()) { autoCorrection = suggestedWords.getWord(1); } else { autoCorrection = typedWord; @@ -2051,7 +2013,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar separatorCode); } - private static final WordComposer sEmptyWordComposer = new WordComposer(); public void updateBigramPredictions() { if (mSuggest == null || !isSuggestionsRequested()) return; @@ -2061,12 +2022,20 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar return; } - final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(), - mSettingsValues.mWordSeparators); - SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(sEmptyWordComposer, - prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCorrectionMode); + final SuggestedWords.Builder builder; + if (mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { + final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(), + mSettingsValues.mWordSeparators); + if (!TextUtils.isEmpty(prevWord)) { + builder = mSuggest.getBigramPredictionWordBuilder(prevWord); + } else { + builder = null; + } + } else { + builder = null; + } - if (builder.size() > 0) { + if (null != builder && builder.size() > 0) { // Explicitly supply an empty typed word (the no-second-arg version of // showSuggestions will retrieve the word near the cursor, we don't want that here) showSuggestions(builder.build(), ""); @@ -2356,6 +2325,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar final String action = intent.getAction(); if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { mSubtypeSwitcher.onNetworkStateChanged(intent); + } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { + mFeedbackManager.onRingerModeChanged(); } } }; diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 671fb905d..f17c1d95a 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -83,8 +83,6 @@ public class Suggest implements Dictionary.WordCallback { private static final boolean DBG = LatinImeLogger.sDBG; - private AutoCorrection mAutoCorrection; - private Dictionary mMainDict; private ContactsDictionary mContactsDict; private WhitelistDictionary mWhiteListDictionary; @@ -124,7 +122,6 @@ public class Suggest implements Dictionary.WordCallback { private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) { mWhiteListDictionary = new WhitelistDictionary(context, locale); addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary); - mAutoCorrection = new AutoCorrection(); StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength()); } @@ -219,10 +216,6 @@ public class Suggest implements Dictionary.WordCallback { mAutoCorrectionThreshold = threshold; } - public boolean isAggressiveAutoCorrectionMode() { - return (mAutoCorrectionThreshold == 0); - } - /** * Number of suggestions to generate from the input key sequence. This has * to be a number between 1 and 100 (inclusive). @@ -240,20 +233,6 @@ public class Suggest implements Dictionary.WordCallback { StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength()); } - /** - * Returns a object which represents suggested words that match the list of character codes - * passed in. This object contents will be overwritten the next time this function is called. - * @param wordComposer contains what is currently being typed - * @param prevWordForBigram previous word (used only for bigram) - * @return suggested words object. - */ - public SuggestedWords getSuggestions(final WordComposer wordComposer, - final CharSequence prevWordForBigram, final ProximityInfo proximityInfo, - final int correctionMode) { - return getSuggestedWordBuilder(wordComposer, prevWordForBigram, - proximityInfo, correctionMode).build(); - } - private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) { if (TextUtils.isEmpty(word) || !(all || first)) return word; final int wordLength = word.length(); @@ -281,12 +260,47 @@ public class Suggest implements Dictionary.WordCallback { mSuggestions.add(sb); } + private static final WordComposer sEmptyWordComposer = new WordComposer(); + public SuggestedWords.Builder getBigramPredictionWordBuilder(CharSequence prevWordForBigram) { + LatinImeLogger.onStartSuggestion(prevWordForBigram); + mIsFirstCharCapitalized = false; + mIsAllUpperCase = false; + mTrailingSingleQuotesCount = 0; + collectGarbage(mSuggestions, mPrefMaxSuggestions); + Arrays.fill(mScores, 0); + + // Treating USER_TYPED as UNIGRAM suggestion for logging now. + LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM); + mConsideredWord = ""; + + Arrays.fill(mBigramScores, 0); + collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS); + + CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase(); + if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) { + prevWordForBigram = lowerPrevWord; + } + for (final Dictionary dictionary : mBigramDictionaries.values()) { + dictionary.getBigrams(sEmptyWordComposer, prevWordForBigram, this); + } + // Nothing entered: return all bigrams for the previous word + int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions); + for (int i = 0; i < insertCount; ++i) { + addBigramToSuggestions(mBigramSuggestions.get(i)); + } + + StringUtils.removeDupes(mSuggestions); + + return new SuggestedWords.Builder().addWords(mSuggestions, null) + .setAllowsToBeAutoCorrected(false) + .setHasAutoCorrection(false); + } + // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder public SuggestedWords.Builder getSuggestedWordBuilder( final WordComposer wordComposer, CharSequence prevWordForBigram, final ProximityInfo proximityInfo, final int correctionMode) { LatinImeLogger.onStartSuggestion(prevWordForBigram); - mAutoCorrection.init(); mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsAllUpperCase = wordComposer.isAllUpperCase(); mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); @@ -297,13 +311,19 @@ public class Suggest implements Dictionary.WordCallback { final String consideredWord = mTrailingSingleQuotesCount > 0 ? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount) : typedWord; - if (typedWord != null) { - // Treating USER_TYPED as UNIGRAM suggestion for logging now. - LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, - Dictionary.UNIGRAM); - } + // Treating USER_TYPED as UNIGRAM suggestion for logging now. + LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, + Dictionary.UNIGRAM); mConsideredWord = consideredWord; + // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid" + // but still autocorrected from - in the case the whitelist only capitalizes the word. + // The whitelist should be case-insensitive, so it's not possible to be consistent with + // a boolean flag. Right now this is handled with a slight hack in + // WhitelistDictionary#shouldForciblyAutoCorrectFrom. + final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected( + getUnigramDictionaries(), consideredWord, wordComposer.isFirstCharCapitalized()); + if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) { // At first character typed, search only the bigrams Arrays.fill(mBigramScores, 0); @@ -360,15 +380,21 @@ public class Suggest implements Dictionary.WordCallback { } } } - final String consideredWordString = - consideredWord == null ? null : consideredWord.toString(); CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized, - mWhiteListDictionary.getWhitelistedWord(consideredWordString)); - - mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer, - mSuggestions, mScores, consideredWord, mAutoCorrectionThreshold, correctionMode, - whitelistedWord); + mWhiteListDictionary.getWhitelistedWord(consideredWord)); + + final boolean hasAutoCorrection; + if (CORRECTION_FULL == correctionMode + || CORRECTION_FULL_BIGRAM == correctionMode) { + final CharSequence autoCorrection = + AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer, + mSuggestions, mScores, consideredWord, mAutoCorrectionThreshold, + whitelistedWord); + hasAutoCorrection = (null != autoCorrection); + } else { + hasAutoCorrection = false; + } if (whitelistedWord != null) { if (mTrailingSingleQuotesCount > 0) { @@ -382,38 +408,66 @@ public class Suggest implements Dictionary.WordCallback { } } - if (typedWord != null) { - mSuggestions.add(0, typedWord.toString()); - } + mSuggestions.add(0, typedWord); StringUtils.removeDupes(mSuggestions); + final SuggestedWords.Builder builder; if (DBG) { - double normalizedScore = mAutoCorrection.getNormalizedScore(); + // TODO: this doesn't take into account the fact that removing dupes from mSuggestions + // may have made mScores[] and mSuggestions out of sync. + final CharSequence autoCorrectionSuggestion = mSuggestions.get(0); + final int autoCorrectionSuggestionScore = mScores[0]; + double normalizedScore = BinaryDictionary.calcNormalizedScore( + typedWord, autoCorrectionSuggestion.toString(), + autoCorrectionSuggestionScore); ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList = new ArrayList<SuggestedWords.SuggestedWordInfo>(); - scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); - for (int i = 0; i < mScores.length; ++i) { + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(autoCorrectionSuggestion, "+", + false)); + final int suggestionsSize = mSuggestions.size(); + // Note: i here is the index in mScores[], but the index in mSuggestions is one more + // than i because we added the typed word to mSuggestions without touching mScores. + for (int i = 0; i < mScores.length && i < suggestionsSize - 1; ++i) { if (normalizedScore > 0) { final String scoreThreshold = String.format("%d (%4.2f)", mScores[i], normalizedScore); scoreInfoList.add( - new SuggestedWords.SuggestedWordInfo(scoreThreshold, false)); + new SuggestedWords.SuggestedWordInfo(mSuggestions.get(i + 1), + scoreThreshold, false)); normalizedScore = 0.0; } else { final String score = Integer.toString(mScores[i]); - scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false)); + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(mSuggestions.get(i + 1), + score, false)); } } - for (int i = mScores.length; i < mSuggestions.size(); ++i) { - scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); + for (int i = mScores.length; i < suggestionsSize; ++i) { + scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(mSuggestions.get(i), + "--", false)); } - return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList); + builder = new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList) + .setAllowsToBeAutoCorrected(allowsToBeAutoCorrected) + .setHasAutoCorrection(hasAutoCorrection); + } else { + builder = new SuggestedWords.Builder().addWords(mSuggestions, null) + .setAllowsToBeAutoCorrected(allowsToBeAutoCorrected) + .setHasAutoCorrection(hasAutoCorrection); } - return new SuggestedWords.Builder().addWords(mSuggestions, null); - } - public boolean hasAutoCorrection() { - return mAutoCorrection.hasAutoCorrection(); + boolean autoCorrectionAvailable = hasAutoCorrection; + if (correctionMode == Suggest.CORRECTION_FULL + || correctionMode == Suggest.CORRECTION_FULL_BIGRAM) { + autoCorrectionAvailable |= !allowsToBeAutoCorrected; + } + // Don't auto-correct words with multiple capital letter + autoCorrectionAvailable &= !wordComposer.isMostlyCaps(); + builder.setTypedWordValid(!allowsToBeAutoCorrected).setHasMinimalSuggestion( + autoCorrectionAvailable); + if (allowsToBeAutoCorrected && builder.size() > 1 && mAutoCorrectionThreshold > 0 + && Suggest.shouldBlockAutoCorrectionBySafetyNet(typedWord, builder.getWord(1))) { + builder.setShouldBlockAutoCorrectionBySafetyNet(); + } + return builder; } @Override @@ -563,37 +617,28 @@ public class Suggest implements Dictionary.WordCallback { // TODO: Resolve the inconsistencies between the native auto correction algorithms and // this safety net - public static boolean shouldBlockAutoCorrectionBySafetyNet( - SuggestedWords.Builder suggestedWordsBuilder, Suggest suggest) { + public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord, + final CharSequence suggestion) { // Safety net for auto correction. - // Actually if we hit this safety net, it's actually a bug. - if (suggestedWordsBuilder.size() <= 1 || suggestedWordsBuilder.isTypedWordValid()) { - return false; - } + // Actually if we hit this safety net, it's a bug. // If user selected aggressive auto correction mode, there is no need to use the safety // net. - if (suggest.isAggressiveAutoCorrectionMode()) { - return false; - } - final CharSequence typedWord = suggestedWordsBuilder.getWord(0); // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH, // we should not use net because relatively edit distance can be big. - if (typedWord.length() < Suggest.MINIMUM_SAFETY_NET_CHAR_LENGTH) { + final int typedWordLength = typedWord.length(); + if (typedWordLength < Suggest.MINIMUM_SAFETY_NET_CHAR_LENGTH) { return false; } - final CharSequence suggestionWord = suggestedWordsBuilder.getWord(1); - final int typedWordLength = typedWord.length(); final int maxEditDistanceOfNativeDictionary = (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1; - final int distance = BinaryDictionary.editDistance( - typedWord.toString(), suggestionWord.toString()); + final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString()); if (DBG) { Log.d(TAG, "Autocorrected edit distance = " + distance + ", " + maxEditDistanceOfNativeDictionary); } if (distance > maxEditDistanceOfNativeDictionary) { if (DBG) { - Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestionWord); + Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion); Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. " + "Turning off auto-correction."); } diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index aad975e46..4a51e796d 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -20,44 +20,36 @@ import android.text.TextUtils; import android.view.inputmethod.CompletionInfo; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; public class SuggestedWords { - public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, false, false, - null); + public static final SuggestedWords EMPTY = new SuggestedWords(false, false, false, false, + Collections.<SuggestedWordInfo>emptyList()); - private final List<CharSequence> mWords; public final boolean mTypedWordValid; public final boolean mHasAutoCorrectionCandidate; public final boolean mIsPunctuationSuggestions; - public final boolean mShouldBlockAutoCorrectionBySafetyNet; private final List<SuggestedWordInfo> mSuggestedWordInfoList; - SuggestedWords(List<CharSequence> words, boolean typedWordValid, + SuggestedWords(boolean typedWordValid, boolean hasAutoCorrectionCandidate, boolean isPunctuationSuggestions, boolean shouldBlockAutoCorrectionBySafetyNet, List<SuggestedWordInfo> suggestedWordInfoList) { - if (words != null) { - mWords = words; - } else { - mWords = Collections.emptyList(); - } mTypedWordValid = typedWordValid; - mHasAutoCorrectionCandidate = hasAutoCorrectionCandidate; + mHasAutoCorrectionCandidate = hasAutoCorrectionCandidate + && !shouldBlockAutoCorrectionBySafetyNet; mIsPunctuationSuggestions = isPunctuationSuggestions; - mShouldBlockAutoCorrectionBySafetyNet = shouldBlockAutoCorrectionBySafetyNet; mSuggestedWordInfoList = suggestedWordInfoList; } public int size() { - return mWords.size(); + return mSuggestedWordInfoList.size(); } public CharSequence getWord(int pos) { - return mWords.get(pos); + return mSuggestedWordInfoList.get(pos).mWord; } public SuggestedWordInfo getInfo(int pos) { @@ -69,8 +61,7 @@ public class SuggestedWords { } public boolean willAutoCorrect() { - return !mTypedWordValid && mHasAutoCorrectionCandidate - && !mShouldBlockAutoCorrectionBySafetyNet; + return !mTypedWordValid && mHasAutoCorrectionCandidate; } @Override @@ -79,17 +70,16 @@ public class SuggestedWords { return "SuggestedWords:" + " mTypedWordValid=" + mTypedWordValid + " mHasAutoCorrectionCandidate=" + mHasAutoCorrectionCandidate - + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions - + " mShouldBlockAutoCorrectionBySafetyNet=" + mShouldBlockAutoCorrectionBySafetyNet - + " mWords=" + Arrays.toString(mWords.toArray()); + + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions; } public static class Builder { - private List<CharSequence> mWords = new ArrayList<CharSequence>(); private boolean mTypedWordValid; private boolean mHasMinimalSuggestion; private boolean mIsPunctuationSuggestions; private boolean mShouldBlockAutoCorrectionBySafetyNet; + private boolean mAllowsToBeAutoCorrected; + private boolean mHasAutoCorrection; private List<SuggestedWordInfo> mSuggestedWordInfoList = new ArrayList<SuggestedWordInfo>(); @@ -101,14 +91,15 @@ public class SuggestedWords { List<SuggestedWordInfo> suggestedWordInfoList) { final int N = words.size(); for (int i = 0; i < N; ++i) { + final CharSequence word = words.get(i); SuggestedWordInfo suggestedWordInfo = null; if (suggestedWordInfoList != null) { suggestedWordInfo = suggestedWordInfoList.get(i); } if (suggestedWordInfo == null) { - suggestedWordInfo = new SuggestedWordInfo(); + suggestedWordInfo = new SuggestedWordInfo(word); } - addWord(words.get(i), suggestedWordInfo); + addWord(word, suggestedWordInfo); } return this; } @@ -119,24 +110,27 @@ public class SuggestedWords { public Builder addWord(CharSequence word, CharSequence debugString, boolean isPreviousSuggestedWord) { - SuggestedWordInfo info = new SuggestedWordInfo(debugString, isPreviousSuggestedWord); + SuggestedWordInfo info = new SuggestedWordInfo(word, debugString, + isPreviousSuggestedWord); return addWord(word, info); } - private Builder addWord(CharSequence word, SuggestedWordInfo suggestedWordInfo) { - if (!TextUtils.isEmpty(word)) { - mWords.add(word); + /* package for tests */ + Builder addWord(CharSequence word, SuggestedWordInfo suggestedWordInfo) { + if (!TextUtils.isEmpty(suggestedWordInfo.mWord)) { // It's okay if suggestedWordInfo is null since it's checked where it's used. mSuggestedWordInfoList.add(suggestedWordInfo); } return this; } - public Builder setApplicationSpecifiedCompletions(CompletionInfo[] infos) { + public static List<CharSequence> getFromApplicationSpecifiedCompletions( + final CompletionInfo[] infos) { + final ArrayList<CharSequence> result = new ArrayList<CharSequence>(); for (CompletionInfo info : infos) { - if (null != info) addWord(info.getText()); + if (null != info) result.add(info.getText()); } - return this; + return result; } public Builder setTypedWordValid(boolean typedWordValid) { @@ -159,11 +153,20 @@ public class SuggestedWords { return this; } + public Builder setAllowsToBeAutoCorrected(final boolean allowsToBeAutoCorrected) { + mAllowsToBeAutoCorrected = allowsToBeAutoCorrected; + return this; + } + + public Builder setHasAutoCorrection(final boolean hasAutoCorrection) { + mHasAutoCorrection = hasAutoCorrection; + return this; + } + // Should get rid of the first one (what the user typed previously) from suggestions // and replace it with what the user currently typed. public Builder addTypedWordAndPreviousSuggestions(CharSequence typedWord, SuggestedWords previousSuggestions) { - mWords.clear(); mSuggestedWordInfoList.clear(); final HashSet<String> alreadySeen = new HashSet<String>(); addWord(typedWord, null, false); @@ -183,21 +186,25 @@ public class SuggestedWords { } public SuggestedWords build() { - return new SuggestedWords(mWords, mTypedWordValid, mHasMinimalSuggestion, + return new SuggestedWords(mTypedWordValid, mHasMinimalSuggestion, mIsPunctuationSuggestions, mShouldBlockAutoCorrectionBySafetyNet, mSuggestedWordInfoList); } public int size() { - return mWords.size(); + return mSuggestedWordInfoList.size(); } public CharSequence getWord(int pos) { - return mWords.get(pos); + return mSuggestedWordInfoList.get(pos).mWord; + } + + public boolean allowsToBeAutoCorrected() { + return mAllowsToBeAutoCorrected; } - public boolean isTypedWordValid() { - return mTypedWordValid; + public boolean hasAutoCorrection() { + return mHasAutoCorrection; } @Override @@ -208,21 +215,24 @@ public class SuggestedWords { + " mHasMinimalSuggestion=" + mHasMinimalSuggestion + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions + " mShouldBlockAutoCorrectionBySafetyNet=" - + mShouldBlockAutoCorrectionBySafetyNet - + " mWords=" + Arrays.toString(mWords.toArray()); + + mShouldBlockAutoCorrectionBySafetyNet; } } public static class SuggestedWordInfo { + private final CharSequence mWord; private final CharSequence mDebugString; private final boolean mPreviousSuggestedWord; - public SuggestedWordInfo() { + public SuggestedWordInfo(final CharSequence word) { + mWord = word; mDebugString = ""; mPreviousSuggestedWord = false; } - public SuggestedWordInfo(CharSequence debugString, boolean previousSuggestedWord) { + public SuggestedWordInfo(final CharSequence word, final CharSequence debugString, + final boolean previousSuggestedWord) { + mWord = word; mDebugString = debugString; mPreviousSuggestedWord = previousSuggestedWord; } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java index 075fb68ee..812376de0 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java @@ -60,6 +60,7 @@ import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.Suggest; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; @@ -328,9 +329,12 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } else { color = mColorTypedWord; } - if (LatinImeLogger.sDBG) { + if (LatinImeLogger.sDBG && suggestedWords.size() > 1) { + // If we auto-correct, then the autocorrection is in slot 0 and the typed word + // is in slot 1. if (index == mCenterSuggestionIndex && suggestedWords.mHasAutoCorrectionCandidate - && suggestedWords.mShouldBlockAutoCorrectionBySafetyNet) { + && Suggest.shouldBlockAutoCorrectionBySafetyNet( + suggestedWords.getWord(1).toString(), suggestedWords.getWord(0))) { return 0xFFFF0000; } } |