diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java new file mode 100644 index 000000000..d1154d9ed --- /dev/null +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -0,0 +1,103 @@ +/* + * 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.android.inputmethod.latin; + +import android.util.Log; + +import java.util.ArrayList; + +public class AutoCorrection { + private static final boolean DBG = LatinImeLogger.sDBG; + private static final String TAG = AutoCorrection.class.getSimpleName(); + private boolean mHasAutoCorrection; + private CharSequence mAutoCorrectionWord; + private double mNormalizedScore; + + public void init() { + mHasAutoCorrection = false; + mAutoCorrectionWord = null; + mNormalizedScore = Integer.MIN_VALUE; + } + + public boolean hasAutoCorrection() { + return mHasAutoCorrection; + } + + public CharSequence getAutoCorrectionWord() { + return mAutoCorrectionWord; + } + + public double getNormalizedScore() { + return mNormalizedScore; + } + + public void updateAutoCorrectionStatus(Suggest suggest, + WordComposer wordComposer, ArrayList suggestions, int[] priorities, + CharSequence typedWord, double autoCorrectionThreshold, int correctionMode, + CharSequence quickFixedWord) { + if (hasAutoCorrectionForTypedWord( + suggest, wordComposer, suggestions, typedWord, correctionMode)) { + mHasAutoCorrection = true; + mAutoCorrectionWord = typedWord; + } else if (hasAutoCorrectForBinaryDictionary(wordComposer, suggestions, correctionMode, + priorities, typedWord, autoCorrectionThreshold)) { + mHasAutoCorrection = true; + mAutoCorrectionWord = suggestions.get(0); + } else if (hasAutoCorrectionForQuickFix(quickFixedWord)) { + mHasAutoCorrection = true; + mAutoCorrectionWord = quickFixedWord; + } + } + + private boolean hasAutoCorrectionForTypedWord(Suggest suggest, WordComposer wordComposer, + ArrayList suggestions, CharSequence typedWord, int correctionMode) { + return wordComposer.size() > 1 && suggestions.size() > 0 && suggest.isValidWord(typedWord) + && (correctionMode == Suggest.CORRECTION_FULL + || correctionMode == Suggest.CORRECTION_FULL_BIGRAM); + } + + private boolean hasAutoCorrectForBinaryDictionary(WordComposer wordComposer, + ArrayList suggestions, int correctionMode, int[] priorities, + CharSequence typedWord, double autoCorrectionThreshold) { + if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL + || correctionMode == Suggest.CORRECTION_FULL_BIGRAM) + && typedWord != null && suggestions.size() > 0 && priorities.length > 0) { + final CharSequence autoCorrectionCandidate = suggestions.get(0); + final int autoCorrectionCandidateScore = priorities[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 = Utils.calcNormalizedScore( + typedWord,autoCorrectionCandidate, autoCorrectionCandidateScore); + if (DBG) { + Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionCandidate + "," + + autoCorrectionCandidateScore + ", " + mNormalizedScore + + "(" + autoCorrectionThreshold + ")"); + } + if (mNormalizedScore >= autoCorrectionThreshold) { + if (DBG) { + Log.d(TAG, "Auto corrected by S-threshold."); + } + return true; + } + } + return false; + } + + private boolean hasAutoCorrectionForQuickFix(CharSequence quickFixedWord) { + return quickFixedWord != null; + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 838249a39..611d36216 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1538,7 +1538,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void showSuggestions(WordComposer word) { - // TODO Maybe need better way of retrieving previous word + // TODO: May need a better way of retrieving previous word CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(), mWordSeparators); SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( @@ -2059,6 +2059,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void updateCorrectionMode() { + // TODO: cleanup messy flags mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false; mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) && !mInputTypeNoAutoCorrect && mHasDictionary; diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index ee6930d93..95a2d631b 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -67,6 +67,8 @@ public class Suggest implements Dictionary.WordCallback { private static final boolean DBG = LatinImeLogger.sDBG; + private AutoCorrection mAutoCorrection; + private BinaryDictionary mMainDict; private Dictionary mUserDictionary; @@ -90,7 +92,6 @@ public class Suggest implements Dictionary.WordCallback { private ArrayList mSuggestions = new ArrayList(); ArrayList mBigramSuggestions = new ArrayList(); private ArrayList mStringPool = new ArrayList(); - private boolean mHasAutoCorrection; private String mLowerOriginalWord; // TODO: Remove these member variables by passing more context to addWord() callback method @@ -101,12 +102,16 @@ public class Suggest implements Dictionary.WordCallback { public Suggest(Context context, int dictionaryResId) { mMainDict = BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN); - initPool(); + init(); } - // For unit test - /* package */ Suggest(File dictionary, long startOffset, long length) { + /* package for test */ Suggest(File dictionary, long startOffset, long length) { mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN); + init(); + } + + private void init() { + mAutoCorrection = new AutoCorrection(); initPool(); } @@ -205,7 +210,7 @@ public class Suggest implements Dictionary.WordCallback { public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer, CharSequence prevWordForBigram) { LatinImeLogger.onStartSuggestion(prevWordForBigram); - mHasAutoCorrection = false; + mAutoCorrection.init(); mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsAllUpperCase = wordComposer.isAllUpperCase(); collectGarbage(mSuggestions, mPrefMaxSuggestions); @@ -224,7 +229,6 @@ public class Suggest implements Dictionary.WordCallback { mLowerOriginalWord = ""; } - double normalizedScore = Integer.MIN_VALUE; if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM || mCorrectionMode == CORRECTION_BASIC)) { // At first character typed, search only the bigrams @@ -273,86 +277,67 @@ public class Suggest implements Dictionary.WordCallback { if (mContactsDictionary != null) { mContactsDictionary.getWords(wordComposer, this); } - - if (mSuggestions.size() > 0 && isValidWord(typedWord) - && (mCorrectionMode == CORRECTION_FULL - || mCorrectionMode == CORRECTION_FULL_BIGRAM)) { - if (DBG) { - Log.d(TAG, "Auto corrected by CORRECTION_FULL."); - } - mHasAutoCorrection = true; - } } if (mMainDict != null) mMainDict.getWords(wordComposer, this); - if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM) - && mSuggestions.size() > 0 && mPriorities.length > 0) { - // TODO: when the normalized score of the first suggestion is nearly equals to - // the normalized score of the second suggestion, behave less aggressive. - normalizedScore = Utils.calcNormalizedScore( - typedWord, mSuggestions.get(0), mPriorities[0]); - if (DBG) { - Log.d(TAG, "Normalized " + typedWord + "," + mSuggestions.get(0) + "," - + mPriorities[0] + ", " + normalizedScore - + "(" + mAutoCorrectionThreshold + ")"); - } - if (normalizedScore >= mAutoCorrectionThreshold) { - if (DBG) { - Log.d(TAG, "Auto corrected by S-threshold."); - } - mHasAutoCorrection = true; - } - } } + CharSequence autoText = null; + final String typedWordString = typedWord == null ? null : typedWord.toString(); if (typedWord != null) { - mSuggestions.add(0, typedWord.toString()); - } - if (mQuickFixesEnabled) { - int i = 0; - int max = 6; - // Don't autotext the suggestions from the dictionaries - if (mCorrectionMode == CORRECTION_BASIC) max = 1; - while (i < mSuggestions.size() && i < max) { - String suggestedWord = mSuggestions.get(i).toString().toLowerCase(); - CharSequence autoText = - AutoText.get(suggestedWord, 0, suggestedWord.length(), view); + // Apply quick fix only for the typed word. + if (mQuickFixesEnabled) { + final String lowerCaseTypedWord = typedWordString.toLowerCase(); + CharSequence tempAutoText = + AutoText.get(lowerCaseTypedWord, 0, lowerCaseTypedWord.length(), view); // Is there an AutoText (also known as Quick Fixes) correction? - boolean canAdd = autoText != null; // Capitalize as needed - final int autoTextLength = autoText != null ? autoText.length() : 0; - if (autoTextLength > 0 && (mIsAllUpperCase || mIsFirstCharCapitalized)) { - int poolSize = mStringPool.size(); - StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove( - poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); + if (!TextUtils.isEmpty(tempAutoText) + && (mIsAllUpperCase || mIsFirstCharCapitalized)) { + final int tempAutoTextLength = tempAutoText.length(); + final int poolSize = mStringPool.size(); + final StringBuilder sb = + poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) + : new StringBuilder(getApproxMaxWordLength()); sb.setLength(0); if (mIsAllUpperCase) { - sb.append(autoText.toString().toUpperCase()); + sb.append(tempAutoText.toString().toUpperCase()); } else if (mIsFirstCharCapitalized) { - sb.append(Character.toUpperCase(autoText.charAt(0))); - if (autoTextLength > 1) { - sb.append(autoText.subSequence(1, autoTextLength)); + sb.append(Character.toUpperCase(tempAutoText.charAt(0))); + if (tempAutoTextLength > 1) { + sb.append(tempAutoText.subSequence(1, tempAutoTextLength)); } } - autoText = sb.toString(); + tempAutoText = sb.toString(); } + boolean canAdd = tempAutoText != null; // Is that correction already the current prediction (or original word)? - canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i)); + canAdd &= !TextUtils.equals(tempAutoText, typedWord); // Is that correction already the next predicted word? - if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) { - canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1)); + if (canAdd && mSuggestions.size() > 0 && mCorrectionMode != CORRECTION_BASIC) { + canAdd &= !TextUtils.equals(tempAutoText, mSuggestions.get(0)); } if (canAdd) { if (DBG) { Log.d(TAG, "Auto corrected by AUTOTEXT."); } - mHasAutoCorrection = true; - mSuggestions.add(i + 1, autoText); - i++; + autoText = tempAutoText; } - i++; } } + + mAutoCorrection.updateAutoCorrectionStatus(this, wordComposer, mSuggestions, mPriorities, + typedWord, mAutoCorrectionThreshold, mCorrectionMode, autoText); + + if (autoText != null) { + mSuggestions.add(0, autoText); + } + + if (typedWord != null) { + mSuggestions.add(0, typedWordString); + } removeDupes(); + if (DBG) { + double normalizedScore = mAutoCorrection.getNormalizedScore(); ArrayList frequencyInfoList = new ArrayList(); frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); @@ -373,9 +358,8 @@ public class Suggest implements Dictionary.WordCallback { frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); } return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList); - } else { - return new SuggestedWords.Builder().addWords(mSuggestions, null); } + return new SuggestedWords.Builder().addWords(mSuggestions, null); } private void removeDupes() { @@ -406,7 +390,7 @@ public class Suggest implements Dictionary.WordCallback { } public boolean hasAutoCorrection() { - return mHasAutoCorrection; + return mAutoCorrection.hasAutoCorrection(); } private static boolean compareCaseInsensitive(final String lowerOriginalWord,