diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
9 files changed, 473 insertions, 135 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index b6035e15e..d87672c0e 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; import android.util.Log; +import java.io.File; import java.util.Arrays; /** @@ -72,9 +73,40 @@ public class BinaryDictionary extends Dictionary { public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) { synchronized (sInstance) { sInstance.closeInternal(); - if (resId != 0) { - sInstance.loadDictionary(context, resId); + try { + final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); + if (afd == null) { + Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); + return null; + } + final String sourceDir = context.getApplicationInfo().sourceDir; + final File packagePath = new File(sourceDir); + // TODO: Come up with a way to handle a directory. + if (!packagePath.isFile()) { + Log.e(TAG, "sourceDir is not a file: " + sourceDir); + return null; + } + sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength()); sInstance.mDicTypeId = dicTypeId; + } catch (android.content.res.Resources.NotFoundException e) { + Log.e(TAG, "Could not find the resource. resId=" + resId); + return null; + } + } + return sInstance; + } + + // For unit test + /* package */ static BinaryDictionary initDictionary(File dictionary, long startOffset, + long length, int dicTypeId) { + synchronized (sInstance) { + sInstance.closeInternal(); + if (dictionary.isFile()) { + sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length); + sInstance.mDicTypeId = dicTypeId; + } else { + Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath()); + return null; } } return sInstance; @@ -92,22 +124,11 @@ public class BinaryDictionary extends Dictionary { int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies, int maxWordLength, int maxBigrams, int maxAlternatives); - private final void loadDictionary(Context context, int resId) { - try { - final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); - if (afd == null) { - Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); - return; - } - mNativeDict = openNative(context.getApplicationInfo().sourceDir, - afd.getStartOffset(), afd.getLength(), + private final void loadDictionary(String path, long startOffset, long length) { + mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES); - mDictLength = afd.getLength(); - } catch (android.content.res.Resources.NotFoundException e) { - Log.e(TAG, "Could not find the resource. resId=" + resId); - return; - } + mDictLength = length; } @Override diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index d2d1f22dd..9699ad136 100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -16,8 +16,11 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; + import android.content.Context; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.Typeface; import android.os.Handler; import android.os.Message; @@ -43,6 +46,7 @@ import android.widget.PopupWindow; import android.widget.TextView; import java.util.ArrayList; +import java.util.List; public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener { @@ -50,6 +54,8 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); private static final int MAX_SUGGESTIONS = 16; + private static final boolean DBG = LatinImeLogger.sDBG; + private final ArrayList<View> mWords = new ArrayList<View>(); private final boolean mConfigCandidateHighlightFontColorEnabled; private final CharacterStyle mInvertedForegroundColorSpan; @@ -175,11 +181,12 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo final SuggestedWords suggestions = mSuggestions; clear(); final int count = suggestions.size(); - final Object[] debugInfo = suggestions.mDebugInfo; for (int i = 0; i < count; i++) { CharSequence word = suggestions.getWord(i); if (word == null) continue; final int wordLength = word.length(); + final List<SuggestedWordInfo> suggestedWordInfoList = + suggestions.mSuggestedWordInfoList; final View v = mWords.get(i); final TextView tv = (TextView)v.findViewById(R.id.candidate_word); @@ -209,10 +216,25 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo } tv.setText(word); tv.setClickable(true); - if (debugInfo != null && i < debugInfo.length && debugInfo[i] != null - && !TextUtils.isEmpty(debugInfo[i].toString())) { - dv.setText(debugInfo[i].toString()); - dv.setVisibility(VISIBLE); + + if (suggestedWordInfoList != null && suggestedWordInfoList.get(i) != null) { + final SuggestedWordInfo info = suggestedWordInfoList.get(i); + if (info.isPreviousSuggestedWord()) { + int color = tv.getCurrentTextColor(); + tv.setTextColor(Color.argb((int)(Color.alpha(color) * 0.5f), Color.red(color), + Color.green(color), Color.blue(color))); + } + final String debugString = info.getDebugString(); + if (DBG) { + if (TextUtils.isEmpty(debugString)) { + dv.setVisibility(GONE); + } else { + dv.setText(debugString); + dv.setVisibility(VISIBLE); + } + } else { + dv.setVisibility(GONE); + } } else { dv.setVisibility(GONE); } @@ -231,8 +253,10 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word); final Spannable word = new SpannableString(autoCorrectedWord); final int wordLength = word.length(); - word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - word.setSpan(mInvertedForegroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + word.setSpan(mInvertedForegroundColorSpan, 0, wordLength, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); tv.setText(word); mShowingAutoCorrectionInverted = true; } diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java index 03211f36b..2f1e7c2b8 100644 --- a/java/src/com/android/inputmethod/latin/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/DebugSettings.java @@ -20,6 +20,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; +import android.os.Process; import android.preference.CheckBoxPreference; import android.preference.PreferenceActivity; import android.util.Log; @@ -30,6 +31,7 @@ public class DebugSettings extends PreferenceActivity private static final String TAG = "DebugSettings"; private static final String DEBUG_MODE_KEY = "debug_mode"; + private boolean mServiceNeedsRestart = false; private CheckBoxPreference mDebugMode; @Override @@ -39,16 +41,24 @@ public class DebugSettings extends PreferenceActivity SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); prefs.registerOnSharedPreferenceChangeListener(this); + mServiceNeedsRestart = false; mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY); updateDebugMode(); } @Override + protected void onStop() { + super.onStop(); + if (mServiceNeedsRestart) Process.killProcess(Process.myPid()); + } + + @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (key.equals(DEBUG_MODE_KEY)) { if (mDebugMode != null) { mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false)); updateDebugMode(); + mServiceNeedsRestart = true; } } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index b6042c769..a55ee5246 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -38,6 +38,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.inputmethodservice.InputMethodService; import android.media.AudioManager; +import android.net.ConnectivityManager; import android.os.Debug; import android.os.Handler; import android.os.Message; @@ -67,6 +68,8 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +// @@@ import android.view.inputmethod.InputMethodSubtype; +import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; @@ -83,22 +86,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = "LatinIME"; private static final boolean PERF_DEBUG = false; - private static final boolean DEBUG = false; private static final boolean TRACE = false; + private static boolean DEBUG = LatinImeLogger.sDBG; private static final int DELAY_UPDATE_SUGGESTIONS = 180; private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300; private static final int DELAY_UPDATE_SHIFT_STATE = 300; + private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100; // How many continuous deletes at which to start deleting at a higher speed. private static final int DELETE_ACCELERATE_AT = 20; // Key events coming any faster than this are long-presses. private static final int QUICK_PRESS = 200; - // Contextual menu positions - private static final int POS_METHOD = 0; - private static final int POS_SETTINGS = 1; - private int mSuggestionVisibility; private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE = R.string.prefs_suggestion_visibility_show_value; @@ -121,6 +121,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private AlertDialog mOptionsDialog; private InputMethodManager mImm; + private Resources mResources; + private SharedPreferences mPrefs; + private String mInputMethodId; private KeyboardSwitcher mKeyboardSwitcher; private SubtypeSwitcher mSubtypeSwitcher; private VoiceIMEConnector mVoiceConnector; @@ -130,9 +133,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private ContactsDictionary mContactsDictionary; private AutoDictionary mAutoDictionary; - private Resources mResources; - private SharedPreferences mPrefs; - // These variables are initialized according to the {@link EditorInfo#inputType}. private boolean mAutoSpace; private boolean mInputTypeNoAutoCorrect; @@ -154,6 +154,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private boolean mPopupOn; private boolean mAutoCap; private boolean mQuickFixes; + private boolean mConfigEnableShowSubtypeSettings; private boolean mConfigSwipeDownDismissKeyboardEnabled; private int mConfigDelayBeforeFadeoutLanguageOnSpacebar; private int mConfigDurationOfFadeoutLanguageOnSpacebar; @@ -348,6 +349,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen super.onCreate(); mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)); + mInputMethodId = Utils.getInputMethodId(mImm, getApplicationInfo().packageName); mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mKeyboardSwitcher = KeyboardSwitcher.getInstance(); @@ -358,11 +360,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // but always use the default setting defined in the resources. if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) { mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED, - res.getBoolean(R.bool.default_recorrection_enabled)); + res.getBoolean(R.bool.config_default_recorrection_enabled)); } else { - mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled); + mReCorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled); } + mConfigEnableShowSubtypeSettings = res.getBoolean( + R.bool.config_enable_show_subtype_settings); mConfigSwipeDownDismissKeyboardEnabled = res.getBoolean( R.bool.config_swipe_down_dismiss_keyboard_enabled); mConfigDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger( @@ -386,8 +390,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mOrientation = res.getConfiguration().orientation; initSuggestPuncList(); - // register to receive ringer mode changes for silent mode - IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + // register to receive ringer mode change and network state change. + final IntentFilter filter = new IntentFilter(); + filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mReceiver, filter); mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler); prefs.registerOnSharedPreferenceChangeListener(this); @@ -460,6 +466,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen commitTyped(ic); if (ic != null) ic.finishComposingText(); // For voice input mOrientation = conf.orientation; + if (isShowingOptionDialog()) + mOptionsDialog.dismiss(); } mConfigurationChanging = true; @@ -506,6 +514,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final KeyboardSwitcher switcher = mKeyboardSwitcher; LatinKeyboardView inputView = switcher.getInputView(); + if(DEBUG) { + Log.d(TAG, "onStartInputView: " + inputView); + } // In landscape mode, this method gets called without the input view being created. if (inputView == null) { return; @@ -862,6 +873,34 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!isFullscreenMode()) { outInsets.contentTopInsets = outInsets.visibleTopInsets; } + KeyboardView inputView = mKeyboardSwitcher.getInputView(); + // Need to set touchable region only if input view is being shown + if (inputView != null && mKeyboardSwitcher.isInputViewShown()) { + final int x = 0; + int y = 0; + final int width = inputView.getWidth(); + int height = inputView.getHeight() + EXTENDED_TOUCHABLE_REGION_HEIGHT; + if (mCandidateViewContainer != null) { + ViewParent candidateParent = mCandidateViewContainer.getParent(); + if (candidateParent instanceof FrameLayout) { + FrameLayout fl = (FrameLayout) candidateParent; + if (fl != null) { + // Check frame layout's visibility + if (fl.getVisibility() == View.INVISIBLE) { + y = fl.getHeight(); + height += y; + } else if (fl.getVisibility() == View.VISIBLE) { + height += fl.getHeight(); + } + } + } + } + if (DEBUG) { + Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height); + } + outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION; + outInsets.touchableRegion.set(x, y, width, height); + } } @Override @@ -1034,7 +1073,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void onSettingsKeyPressed() { if (!isShowingOptionDialog()) { - if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) { + if (!mConfigEnableShowSubtypeSettings) { + showSubtypeSelectorAndSettings(); + } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) { showOptionsMenu(); } else { launchSettings(); @@ -1420,6 +1461,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private boolean isCandidateStripVisible() { + if (mCandidateView == null) + return false; if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isCorrecting()) return true; if (!isShowingSuggestionsStrip()) @@ -1508,7 +1551,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies); boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted - && mSuggest.hasMinimalCorrection(); + && mSuggest.hasAutoCorrection(); final CharSequence typedWord = word.getTypedWord(); // If we're in basic correct final boolean typedWordValid = mSuggest.isValidWord(typedWord) || @@ -1524,10 +1567,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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, 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.length() == 1 || builder.size() > 1 + // 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 (builder.size() > 1 || typedWord.length() == 1 || typedWordValid || mCandidateView.isShowingAddToDictionaryHint()) { builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(correctionAvailable); } else { @@ -1542,7 +1586,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) { setSuggestions(suggestedWords); if (suggestedWords.size() > 0) { - if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords)) { + if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords, mSuggest)) { mBestWord = typedWord; } else if (suggestedWords.hasAutoCorrectionWord()) { mBestWord = suggestedWords.getWord(1); @@ -1912,7 +1956,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) { mReCorrectionEnabled = sharedPreferences.getBoolean( Settings.PREF_RECORRECTION_ENABLED, - mResources.getBoolean(R.bool.default_recorrection_enabled)); + mResources.getBoolean(R.bool.config_default_recorrection_enabled)); } } @@ -1953,11 +1997,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } - // receive ringer mode changes to detect silent mode + // receive ringer mode change and network state change. private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - updateRingerMode(); + final String action = intent.getAction(); + if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { + updateRingerMode(); + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + mSubtypeSwitcher.onNetworkStateChanged(intent); + } } }; @@ -2039,7 +2088,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void updateAutoTextEnabled() { if (mSuggest == null) return; - mSuggest.setAutoTextEnabled(mQuickFixes + mSuggest.setQuickFixesEnabled(mQuickFixes && SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage()); } @@ -2079,9 +2128,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // @@@ mVibrateOn = vibrator != null && vibrator.hasVibrator() mVibrateOn = vibrator != null && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); - mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false); - mPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON, - mResources.getBoolean(R.bool.config_default_popup_preview)); + mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, + mResources.getBoolean(R.bool.config_default_sound_enabled)); + + mPopupOn = isPopupEnabled(prefs); mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); mQuickFixes = isQuickFixesEnabled(prefs); @@ -2132,6 +2182,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSuggest.setAutoCorrectionThreshold(autoCorrectionThreshold); } + private boolean isPopupEnabled(SharedPreferences sp) { + final boolean showPopupOption = getResources().getBoolean( + R.bool.config_enable_show_popup_on_keypress_option); + if (!showPopupOption) return mResources.getBoolean(R.bool.config_default_popup_preview); + return sp.getBoolean(Settings.PREF_POPUP_ON, + mResources.getBoolean(R.bool.config_default_popup_preview)); + } + private boolean isQuickFixesEnabled(SharedPreferences sp) { final boolean showQuickFixesOption = mResources.getBoolean( R.bool.config_enable_quick_fixes_option); @@ -2179,32 +2237,70 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return mSuggestPuncs.contains(String.valueOf((char)code)); } - private void showOptionsMenu() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setCancelable(true); - builder.setIcon(R.drawable.ic_dialog_keyboard); - builder.setNegativeButton(android.R.string.cancel, null); - CharSequence itemSettings = getString(R.string.english_ime_settings); - CharSequence itemInputMethod = getString(R.string.selectInputMethod); - builder.setItems(new CharSequence[] { - itemInputMethod, itemSettings}, - new DialogInterface.OnClickListener() { + private void showSubtypeSelectorAndSettings() { + final CharSequence title = getString(R.string.english_ime_input_options); + final CharSequence[] items = new CharSequence[] { + // TODO: Should use new string "Select active input modes". + getString(R.string.language_selection_title), + getString(R.string.english_ime_settings), + }; + final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface di, int position) { + di.dismiss(); + switch (position) { + case 0: + Intent intent = new Intent( + android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID, + mInputMethodId); + startActivity(intent); + break; + case 1: + launchSettings(); + break; + } + } + }; + showOptionsMenuInternal(title, items, listener); + } + private void showOptionsMenu() { + final CharSequence title = getString(R.string.english_ime_input_options); + final CharSequence[] items = new CharSequence[] { + getString(R.string.selectInputMethod), + getString(R.string.english_ime_settings), + }; + final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface di, int position) { di.dismiss(); switch (position) { - case POS_SETTINGS: - launchSettings(); - break; - case POS_METHOD: - mImm.showInputMethodPicker(); - break; + case 0: + mImm.showInputMethodPicker(); + break; + case 1: + launchSettings(); + break; } } - }); - builder.setTitle(mResources.getString(R.string.english_ime_input_options)); + }; + showOptionsMenuInternal(title, items, listener); + } + + private void showOptionsMenuInternal(CharSequence title, CharSequence[] items, + DialogInterface.OnClickListener listener) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setCancelable(true); + builder.setIcon(R.drawable.ic_dialog_keyboard); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setItems(items, listener); + builder.setTitle(title); mOptionsDialog = builder.create(); + mOptionsDialog.setCanceledOnTouchOutside(true); Window window = mOptionsDialog.getWindow(); WindowManager.LayoutParams lp = window.getAttributes(); lp.token = mKeyboardSwitcher.getInputView().getWindowToken(); diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index c78e6dd07..59c68f208 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -75,6 +75,7 @@ public class Settings extends PreferenceActivity private CheckBoxPreference mQuickFixes; private ListPreference mVoicePreference; private ListPreference mSettingsKeyPreference; + private ListPreference mShowCorrectionSuggestionsPreference; private ListPreference mAutoCorrectionThreshold; private CheckBoxPreference mBigramSuggestion; private boolean mVoiceOn; @@ -102,6 +103,8 @@ public class Settings extends PreferenceActivity mQuickFixes = (CheckBoxPreference) findPreference(PREF_QUICK_FIXES); mVoicePreference = (ListPreference) findPreference(PREF_VOICE_SETTINGS_KEY); mSettingsKeyPreference = (ListPreference) findPreference(PREF_SETTINGS_KEY); + mShowCorrectionSuggestionsPreference = + (ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING); SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); prefs.registerOnSharedPreferenceChangeListener(this); @@ -190,6 +193,7 @@ public class Settings extends PreferenceActivity updateVoiceModeSummary(); } updateSettingsKeySummary(); + updateShowCorrectionSuggestionsSummary(); } @Override @@ -214,6 +218,7 @@ public class Settings extends PreferenceActivity .equals(mVoiceModeOff)); updateVoiceModeSummary(); updateSettingsKeySummary(); + updateShowCorrectionSuggestionsSummary(); } @Override @@ -222,7 +227,9 @@ public class Settings extends PreferenceActivity final String action; if (android.os.Build.VERSION.SDK_INT >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) { - action = "android.settings.INPUT_METHOD_AND_SUBTYPE_ENABLER"; + // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS + // TODO: Can this be a constant instead of literal String constant? + action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; } else { action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION"; } @@ -232,6 +239,13 @@ public class Settings extends PreferenceActivity return false; } + private void updateShowCorrectionSuggestionsSummary() { + mShowCorrectionSuggestionsPreference.setSummary( + getResources().getStringArray(R.array.prefs_suggestion_visibilities) + [mShowCorrectionSuggestionsPreference.findIndexOfValue( + mShowCorrectionSuggestionsPreference.getValue())]); + } + private void updateSettingsKeySummary() { mSettingsKeyPreference.setSummary( getResources().getStringArray(R.array.settings_key_modes) diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 6666c8e15..f45c2b7f8 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -18,16 +18,21 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.InputMethodSubtype; import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.LatinKeyboard; +import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.voice.SettingsUtil; import com.android.inputmethod.voice.VoiceIMEConnector; import com.android.inputmethod.voice.VoiceInput; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; @@ -41,12 +46,14 @@ import java.util.Locale; import java.util.Map; public class SubtypeSwitcher { - private static final boolean DBG = false; + private static boolean DBG = LatinImeLogger.sDBG; private static final String TAG = "SubtypeSwitcher"; private static final char LOCALE_SEPARATER = '_'; private static final String KEYBOARD_MODE = "keyboard"; private static final String VOICE_MODE = "voice"; + private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY = + "requireNetworkConnectivity"; private final TextUtils.SimpleStringSplitter mLocaleSplitter = new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER); @@ -55,17 +62,17 @@ public class SubtypeSwitcher { private /* final */ SharedPreferences mPrefs; private /* final */ InputMethodManager mImm; private /* final */ Resources mResources; + private /* final */ ConnectivityManager mConnectivityManager; + private /* final */ boolean mConfigUseSpacebarLanguageSwitcher; private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod = new ArrayList<InputMethodSubtype>(); private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>(); - private boolean mConfigUseSpacebarLanguageSwitcher; - /*-----------------------------------------------------------*/ // Variants which should be changed only by reload functions. private boolean mNeedsToDisplayLanguage; private boolean mIsSystemLanguageSameAsInputLanguage; - private InputMethodInfo mShortcutInfo; + private InputMethodInfo mShortcutInputMethodInfo; private InputMethodSubtype mShortcutSubtype; private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod; private Locale mSystemLocale; @@ -75,13 +82,14 @@ public class SubtypeSwitcher { private VoiceInput mVoiceInput; /*-----------------------------------------------------------*/ + private boolean mIsNetworkConnected; + public static SubtypeSwitcher getInstance() { return sInstance; } public static void init(LatinIME service, SharedPreferences prefs) { - sInstance.mPrefs = prefs; - sInstance.resetParams(service); + sInstance.initialize(service, prefs); sInstance.updateAllParameters(); SubtypeLocale.init(service); @@ -91,10 +99,13 @@ public class SubtypeSwitcher { // Intentional empty constructor for singleton. } - private void resetParams(LatinIME service) { + private void initialize(LatinIME service, SharedPreferences prefs) { mService = service; + mPrefs = prefs; mResources = service.getResources(); mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE); + mConnectivityManager = (ConnectivityManager) service.getSystemService( + Context.CONNECTIVITY_SERVICE); mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); mEnabledLanguagesOfCurrentInputMethod.clear(); mSystemLocale = null; @@ -109,6 +120,9 @@ public class SubtypeSwitcher { R.bool.config_use_spacebar_language_switcher); if (mConfigUseSpacebarLanguageSwitcher) initLanguageSwitcher(service); + + final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); + mIsNetworkConnected = (info != null && info.isConnected()); } // Update all parameters stored in SubtypeSwitcher. @@ -163,6 +177,13 @@ public class SubtypeSwitcher { } private void updateShortcutIME() { + if (DBG) { + Log.d(TAG, "Update shortcut IME from : " + + (mShortcutInputMethodInfo == null + ? "<null>" : mShortcutInputMethodInfo.getId()) + ", " + + (mShortcutSubtype == null ? "<null>" : (mShortcutSubtype.getLocale() + + ", " + mShortcutSubtype.getMode()))); + } // TODO: Update an icon for shortcut IME /* Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = @@ -171,7 +192,7 @@ public class SubtypeSwitcher { List<InputMethodSubtype> subtypes = shortcuts.get(imi); // TODO: Returns the first found IMI for now. Should handle all shortcuts as // appropriate. - mShortcutInfo = imi; + mShortcutInputMethodInfo = imi; // TODO: Pick up the first found subtype for now. Should handle all subtypes // as appropriate. mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null; @@ -213,6 +234,9 @@ public class SubtypeSwitcher { } mMode = newMode; } + + // If the old mode is voice input, we need to reset or cancel its status. + // We cancel its status when we change mode, while we reset otherwise. if (isKeyboardMode()) { if (modeChanged) { if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { @@ -220,19 +244,26 @@ public class SubtypeSwitcher { } } if (modeChanged || languageChanged) { + updateShortcutIME(); mService.onRefreshKeyboard(); } - } else if (isVoiceMode()) { + } else if (isVoiceMode() && mVoiceInput != null) { + if (VOICE_MODE.equals(oldMode)) { + mVoiceInput.reset(); + } // If needsToShowWarningDialog is true, voice input need to show warning before // show recognition view. if (languageChanged || modeChanged || VoiceIMEConnector.getInstance().needsToShowWarningDialog()) { - if (mVoiceInput != null) { - triggerVoiceIME(); - } + triggerVoiceIME(); } } else { Log.w(TAG, "Unknown subtype mode: " + mMode); + if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) { + // We need to reset the voice input to release the resources and to reset its status + // as it is not the current input mode. + mVoiceInput.reset(); + } } } @@ -268,15 +299,15 @@ public class SubtypeSwitcher { //////////////////////////// public void switchToShortcutIME() { - IBinder token = mService.getWindow().getWindow().getAttributes().token; - if (token == null || mShortcutInfo == null) { + final IBinder token = mService.getWindow().getWindow().getAttributes().token; + if (token == null || mShortcutInputMethodInfo == null) { return; } // @@@ mImm.setInputMethodAndSubtype(token, mShortcutInfo.getId(), mShortcutSubtype); } public Drawable getShortcutIcon() { - return getSubtypeIcon(mShortcutInfo, mShortcutSubtype); + return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype); } private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) { @@ -291,9 +322,9 @@ public class SubtypeSwitcher { if (subtype != null) { return pm.getDrawable(imiPackageName, subtype.getIconResId(), imi.getServiceInfo().applicationInfo); - } else if (imi.getSubtypes().size() > 0 && imi.getSubtypes().get(0) != null) { + } else if (imi.getSubtypeCount() > 0 && imi.getSubtypeAt(0) != null) { return pm.getDrawable(imiPackageName, - imi.getSubtypes().get(0).getIconResId(), + imi.getSubtypeAt(0).getIconResId(), imi.getServiceInfo().applicationInfo); } else { try { @@ -307,6 +338,38 @@ public class SubtypeSwitcher { return null; } + private static boolean contains(String[] hay, String needle) { + for (String element : hay) { + if (element.equals(needle)) + return true; + } + return false; + } + + public boolean isShortcutAvailable() { + if (mShortcutInputMethodInfo == null) + return false; + if (mShortcutSubtype != null && contains(mShortcutSubtype.getExtraValue().split(","), + SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) { + return mIsNetworkConnected; + } + return true; + } + + public void onNetworkStateChanged(Intent intent) { + final boolean noConnection = intent.getBooleanExtra( + ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); + mIsNetworkConnected = !noConnection; + + final LatinKeyboardView inputView = KeyboardSwitcher.getInstance().getInputView(); + if (inputView != null) { + final LatinKeyboard keyboard = inputView.getLatinKeyboard(); + if (keyboard != null) { + keyboard.updateShortcutKey(isShortcutAvailable(), inputView); + } + } + } + ////////////////////////////////// // Language Switching functions // ////////////////////////////////// @@ -353,8 +416,15 @@ public class SubtypeSwitcher { if (mConfigUseSpacebarLanguageSwitcher) { return mLanguageSwitcher.getEnabledLanguages(); } else { + int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size(); + // Workaround for explicitly specifying the voice language + if (enabledLanguageCount == 1) { + mEnabledLanguagesOfCurrentInputMethod.add( + mEnabledLanguagesOfCurrentInputMethod.get(0)); + ++enabledLanguageCount; + } return mEnabledLanguagesOfCurrentInputMethod.toArray( - new String[mEnabledLanguagesOfCurrentInputMethod.size()]); + new String[enabledLanguageCount]); } } @@ -427,7 +497,7 @@ public class SubtypeSwitcher { mVoiceInput = vi; if (isVoiceMode()) { if (DBG) { - Log.d(TAG, "Set and call voice input."); + Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr()); } triggerVoiceIME(); return true; diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 24c73e8ea..c9e57d0a5 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -22,6 +22,7 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -64,7 +65,7 @@ public class Suggest implements Dictionary.WordCallback { static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000; - private static boolean DBG = LatinImeLogger.sDBG; + private static final boolean DBG = LatinImeLogger.sDBG; private BinaryDictionary mMainDict; @@ -80,7 +81,7 @@ public class Suggest implements Dictionary.WordCallback { private static final int PREF_MAX_BIGRAMS = 60; - private boolean mAutoTextEnabled; + private boolean mQuickFixesEnabled; private double mAutoCorrectionThreshold; private int[] mPriorities = new int[mPrefMaxSuggestions]; @@ -95,7 +96,7 @@ public class Suggest implements Dictionary.WordCallback { private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>(); private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>(); - private boolean mHaveAutoCorrection; + private boolean mHasAutoCorrection; private String mLowerOriginalWord; // TODO: Remove these member variables by passing more context to addWord() callback method @@ -109,6 +110,12 @@ public class Suggest implements Dictionary.WordCallback { initPool(); } + // For unit test + /* package */ Suggest(File dictionary, long startOffset, long length) { + mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN); + initPool(); + } + private void initPool() { for (int i = 0; i < mPrefMaxSuggestions; i++) { StringBuilder sb = new StringBuilder(getApproxMaxWordLength()); @@ -116,8 +123,8 @@ public class Suggest implements Dictionary.WordCallback { } } - public void setAutoTextEnabled(boolean enabled) { - mAutoTextEnabled = enabled; + public void setQuickFixesEnabled(boolean enabled) { + mQuickFixesEnabled = enabled; } public int getCorrectionMode() { @@ -163,6 +170,10 @@ 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). @@ -200,7 +211,7 @@ public class Suggest implements Dictionary.WordCallback { public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer, CharSequence prevWordForBigram) { LatinImeLogger.onStartSuggestion(prevWordForBigram); - mHaveAutoCorrection = false; + mHasAutoCorrection = false; mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); mIsAllUpperCase = wordComposer.isAllUpperCase(); collectGarbage(mSuggestions, mPrefMaxSuggestions); @@ -220,6 +231,7 @@ 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 @@ -278,7 +290,7 @@ public class Suggest implements Dictionary.WordCallback { if (DBG) { Log.d(TAG, "Auto corrected by CORRECTION_FULL."); } - mHaveAutoCorrection = true; + mHasAutoCorrection = true; } } if (mMainDict != null) mMainDict.getWords(wordComposer, this, mNextLettersFrequencies); @@ -286,25 +298,25 @@ public class Suggest implements Dictionary.WordCallback { && 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. - final double normalizedScore = Utils.calcNormalizedScore( + normalizedScore = Utils.calcNormalizedScore( typedWord, mSuggestions.get(0), mPriorities[0]); - if (LatinImeLogger.sDBG) { + if (DBG) { Log.d(TAG, "Normalized " + typedWord + "," + mSuggestions.get(0) + "," - + mPriorities[0] + normalizedScore + + mPriorities[0] + ", " + normalizedScore + "(" + mAutoCorrectionThreshold + ")"); } if (normalizedScore >= mAutoCorrectionThreshold) { if (DBG) { Log.d(TAG, "Auto corrected by S-threthhold."); } - mHaveAutoCorrection = true; + mHasAutoCorrection = true; } } } if (typedWord != null) { mSuggestions.add(0, typedWord.toString()); } - if (mAutoTextEnabled) { + if (mQuickFixesEnabled) { int i = 0; int max = 6; // Don't autotext the suggestions from the dictionaries @@ -342,7 +354,7 @@ public class Suggest implements Dictionary.WordCallback { if (DBG) { Log.d(TAG, "Auto corrected by AUTOTEXT."); } - mHaveAutoCorrection = true; + mHasAutoCorrection = true; mSuggestions.add(i + 1, autoText); i++; } @@ -350,7 +362,30 @@ public class Suggest implements Dictionary.WordCallback { } } removeDupes(); - return new SuggestedWords.Builder().addWords(mSuggestions); + if (DBG) { + ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList = + new ArrayList<SuggestedWords.SuggestedWordInfo>(); + frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); + final int priorityLength = mPriorities.length; + for (int i = 0; i < priorityLength; ++i) { + if (normalizedScore > 0) { + final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" + + normalizedScore + ")"; + frequencyInfoList.add( + new SuggestedWords.SuggestedWordInfo(priorityThreshold, false)); + normalizedScore = 0.0; + } else { + final String priority = Integer.toString(mPriorities[i]); + frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false)); + } + } + for (int i = priorityLength; i < mSuggestions.size(); ++i) { + frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); + } + return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList); + } else { + return new SuggestedWords.Builder().addWords(mSuggestions, null); + } } public int[] getNextLettersFrequencies() { @@ -384,16 +419,16 @@ public class Suggest implements Dictionary.WordCallback { } } - public boolean hasMinimalCorrection() { - return mHaveAutoCorrection; + public boolean hasAutoCorrection() { + return mHasAutoCorrection; } - private boolean compareCaseInsensitive(final String mLowerOriginalWord, + private static boolean compareCaseInsensitive(final String lowerOriginalWord, final char[] word, final int offset, final int length) { - final int originalLength = mLowerOriginalWord.length(); + final int originalLength = lowerOriginalWord.length(); if (originalLength == length && Character.isUpperCase(word[offset])) { for (int i = 0; i < originalLength; i++) { - if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { + if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { return false; } } diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 0fbbcdd91..f774ce3a5 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -20,6 +20,7 @@ import android.view.inputmethod.CompletionInfo; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; public class SuggestedWords { @@ -29,10 +30,11 @@ public class SuggestedWords { public final boolean mIsApplicationSpecifiedCompletions; public final boolean mTypedWordValid; public final boolean mHasMinimalSuggestion; - public final Object[] mDebugInfo; + public final List<SuggestedWordInfo> mSuggestedWordInfoList; private SuggestedWords(List<CharSequence> words, boolean isApplicationSpecifiedCompletions, - boolean typedWordValid, boolean hasMinamlSuggestion, Object[] debugInfo) { + boolean typedWordValid, boolean hasMinamlSuggestion, + List<SuggestedWordInfo> suggestedWordInfoList) { if (words != null) { mWords = words; } else { @@ -41,7 +43,7 @@ public class SuggestedWords { mIsApplicationSpecifiedCompletions = isApplicationSpecifiedCompletions; mTypedWordValid = typedWordValid; mHasMinimalSuggestion = hasMinamlSuggestion; - mDebugInfo = debugInfo; + mSuggestedWordInfoList = suggestedWordInfoList; } public int size() { @@ -61,38 +63,46 @@ public class SuggestedWords { } public static class Builder { - private List<CharSequence> mWords; + private List<CharSequence> mWords = new ArrayList<CharSequence>(); private boolean mIsCompletions; private boolean mTypedWordValid; private boolean mHasMinimalSuggestion; - private Object[] mDebugInfo; + private List<SuggestedWordInfo> mSuggestedWordInfoList = + new ArrayList<SuggestedWordInfo>(); public Builder() { // Nothing to do here. } - public Builder addWords(List<CharSequence> words) { - for (final CharSequence word : words) - addWord(word); + public Builder addWords(List<CharSequence> words, + List<SuggestedWordInfo> suggestedWordInfoList) { + final int N = words.size(); + for (int i = 0; i < N; ++i) { + SuggestedWordInfo suggestedWordInfo = null; + if (suggestedWordInfoList != null) { + suggestedWordInfo = suggestedWordInfoList.get(i); + } + if (suggestedWordInfo == null) { + suggestedWordInfo = new SuggestedWordInfo(); + } + addWord(words.get(i), suggestedWordInfo); + } return this; } - public Builder setDebugInfo(Object[] debuginfo) { - mDebugInfo = debuginfo; - return this; + public Builder addWord(CharSequence word) { + return addWord(word, null, false); } - public Builder addWord(int pos, CharSequence word) { - if (mWords == null) - mWords = new ArrayList<CharSequence>(); - mWords.add(pos, word); - return this; + public Builder addWord(CharSequence word, CharSequence debugString, + boolean isPreviousSuggestedWord) { + SuggestedWordInfo info = new SuggestedWordInfo(debugString, isPreviousSuggestedWord); + return addWord(word, info); } - public Builder addWord(CharSequence word) { - if (mWords == null) - mWords = new ArrayList<CharSequence>(); + private Builder addWord(CharSequence word, SuggestedWordInfo suggestedWordInfo) { mWords.add(word); + mSuggestedWordInfoList.add(suggestedWordInfo); return this; } @@ -117,11 +127,20 @@ public class SuggestedWords { // and replace it with what the user currently typed. public Builder addTypedWordAndPreviousSuggestions(CharSequence typedWord, SuggestedWords previousSuggestions) { - if (mWords != null) mWords.clear(); - addWord(typedWord); + mWords.clear(); + mSuggestedWordInfoList.clear(); + final HashSet<String> alreadySeen = new HashSet<String>(); + addWord(typedWord, null, false); + alreadySeen.add(typedWord.toString()); final int previousSize = previousSuggestions.size(); - for (int pos = 1; pos < previousSize; pos++) - addWord(previousSuggestions.getWord(pos)); + for (int pos = 1; pos < previousSize; pos++) { + final String prevWord = previousSuggestions.getWord(pos).toString(); + // Filter out duplicate suggestion. + if (!alreadySeen.contains(prevWord)) { + addWord(prevWord, null, true); + alreadySeen.add(prevWord); + } + } mIsCompletions = false; mTypedWordValid = false; mHasMinimalSuggestion = false; @@ -130,15 +149,42 @@ public class SuggestedWords { public SuggestedWords build() { return new SuggestedWords(mWords, mIsCompletions, mTypedWordValid, - mHasMinimalSuggestion, mDebugInfo); + mHasMinimalSuggestion, mSuggestedWordInfoList); } public int size() { - return mWords == null ? 0 : mWords.size(); + return mWords.size(); } public CharSequence getWord(int pos) { return mWords.get(pos); } } + + public static class SuggestedWordInfo { + private final CharSequence mDebugString; + private final boolean mPreviousSuggestedWord; + + public SuggestedWordInfo() { + mDebugString = ""; + mPreviousSuggestedWord = false; + } + + public SuggestedWordInfo(CharSequence debugString, boolean previousSuggestedWord) { + mDebugString = debugString; + mPreviousSuggestedWord = previousSuggestedWord; + } + + public String getDebugString() { + if (mDebugString == null) { + return ""; + } else { + return mDebugString.toString(); + } + } + + public boolean isPreviousSuggestedWord () { + return mPreviousSuggestedWord; + } + } } diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 160948507..d33d962c0 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -23,6 +23,7 @@ import android.os.HandlerThread; import android.os.Process; import android.text.format.DateUtils; import android.util.Log; +import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import java.io.BufferedReader; @@ -37,6 +38,7 @@ import java.util.Date; public class Utils { private static final String TAG = Utils.class.getSimpleName(); + private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4; private static boolean DBG = LatinImeLogger.sDBG; /** @@ -98,12 +100,26 @@ public class Utils { // || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; } + public static String getInputMethodId(InputMethodManager imm, String packageName) { + for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { + if (imi.getPackageName().equals(packageName)) + return imi.getId(); + } + throw new RuntimeException("Can not find input method id for " + packageName); + } - public static boolean shouldBlockedBySafetyNetForAutoCorrection(SuggestedWords suggestions) { + public static boolean shouldBlockedBySafetyNetForAutoCorrection(SuggestedWords suggestions, + Suggest suggest) { // Safety net for auto correction. // Actually if we hit this safety net, it's actually a bug. if (suggestions.size() <= 1 || suggestions.mTypedWordValid) return false; + // If user selected aggressive auto correction mode, there is no need to use the safety + // net. + if (suggest.isAggressiveAutoCorrectionMode()) return false; CharSequence typedWord = suggestions.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() < MINIMUM_SAFETY_NET_CHAR_LENGTH) return false; CharSequence candidateWord = suggestions.getWord(1); final int typedWordLength = typedWord.length(); final int maxEditDistanceOfNativeDictionary = typedWordLength < 5 ? 2 : typedWordLength / 2; @@ -113,8 +129,11 @@ public class Utils { + ", " + maxEditDistanceOfNativeDictionary); } if (distance > maxEditDistanceOfNativeDictionary) { - Log.w(TAG, "(Error) The edit distance of this correction exceeds limit. " - + "Turning off auto-correction."); + if (DBG) { + Log.d(TAG, "Safety net: before = " + typedWord + ", after = " + candidateWord); + Log.w(TAG, "(Error) The edit distance of this correction exceeds limit. " + + "Turning off auto-correction."); + } return true; } else { return false; @@ -260,9 +279,12 @@ public class Utils { public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) { final int beforeLength = before.length(); final int afterLength = after.length(); + if (beforeLength == 0 || afterLength == 0) return 0; final int distance = editDistance(before, after); + // If afterLength < beforeLength, the algorithm is suggesting a word by excessive character + // correction. final double maximumScore = MAX_INITIAL_SCORE - * Math.pow(TYPED_LETTER_MULTIPLIER, beforeLength) + * Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength)) * FULL_WORD_MULTIPLYER; // add a weight based on edit distance. // distance <= max(afterLength, beforeLength) == afterLength, |