diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/LatinIME.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/LatinIME.java | 507 |
1 files changed, 307 insertions, 200 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index c464a7067..719c7c81f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -43,7 +43,6 @@ import android.os.Message; import android.os.SystemClock; import android.preference.PreferenceManager; import android.text.InputType; -import android.text.SpannableString; import android.text.TextUtils; import android.text.style.SuggestionSpan; import android.util.Log; @@ -74,11 +73,29 @@ import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.latin.RichInputConnection.Range; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.Utils.Stats; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper; +import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary; +import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary; +import com.android.inputmethod.latin.settings.Settings; +import com.android.inputmethod.latin.settings.SettingsActivity; +import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.suggestions.SuggestionStripView; +import com.android.inputmethod.latin.utils.ApplicationUtils; +import com.android.inputmethod.latin.utils.AutoCorrectionUtils; +import com.android.inputmethod.latin.utils.CapsModeUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.CompletionInfoUtils; +import com.android.inputmethod.latin.utils.InputTypeUtils; +import com.android.inputmethod.latin.utils.IntentUtils; +import com.android.inputmethod.latin.utils.JniUtils; +import com.android.inputmethod.latin.utils.LatinImeLoggerUtils; +import com.android.inputmethod.latin.utils.PositionalInfoForUserDictPendingAddition; +import com.android.inputmethod.latin.utils.RecapitalizeStatus; +import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; +import com.android.inputmethod.latin.utils.TextRange; import com.android.inputmethod.research.ResearchLogger; import java.io.FileDescriptor; @@ -139,7 +156,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private SuggestionStripView mSuggestionStripView; // Never null private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - @UsedForTesting Suggest mSuggest; + private Suggest mSuggest; private CompletionInfo[] mApplicationSpecifiedCompletions; private AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils(); @@ -153,7 +170,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private boolean mIsMainDictionaryAvailable; private UserBinaryDictionary mUserDictionary; - private UserHistoryDictionary mUserHistoryDictionary; + private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary; + private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary; private boolean mIsUserDictionaryAvailable; private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; @@ -179,11 +197,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private int mDisplayOrientation; // Object for reacting to adding/removing a dictionary pack. - // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack - // Service yet. private BroadcastReceiver mDictionaryPackInstallReceiver = - ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS - ? null : new DictionaryPackInstallBroadcastReceiver(this); + new DictionaryPackInstallBroadcastReceiver(this); // Keeps track of most recently inserted text (multi-character key) for reverting private String mEnteredText; @@ -204,6 +219,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final int MSG_UPDATE_SUGGESTION_STRIP = 2; private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3; private static final int MSG_RESUME_SUGGESTIONS = 4; + private static final int MSG_REOPEN_DICTIONARIES = 5; private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; @@ -244,6 +260,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case MSG_RESUME_SUGGESTIONS: latinIme.restartSuggestionsOnWordTouchedByCursor(); break; + case MSG_REOPEN_DICTIONARIES: + latinIme.initSuggest(); + // In theory we could call latinIme.updateSuggestionStrip() right away, but + // in the practice, the dictionary is not finished opening yet so we wouldn't + // get any suggestions. Wait one frame. + postUpdateSuggestionStrip(); + break; } } @@ -251,6 +274,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions); } + public void postReopenDictionaries() { + sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES)); + } + public void postResumeSuggestions() { removeMessages(MSG_RESUME_SUGGESTIONS); sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions); @@ -264,6 +291,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return hasMessages(MSG_UPDATE_SUGGESTION_STRIP); } + public boolean hasPendingReopenDictionaries() { + return hasMessages(MSG_REOPEN_DICTIONARIES); + } + public void postUpdateShiftState() { removeMessages(MSG_UPDATE_SHIFT_STATE); sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState); @@ -416,6 +447,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } + // Loading the native library eagerly to avoid unexpected UnsatisfiedLinkError at the initial + // JNI call as much as possible. + static { + JniUtils.loadNativeLibrary(); + } + public LatinIME() { super(); mSettings = Settings.getInstance(); @@ -458,19 +495,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); registerReceiver(mReceiver, filter); - // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack - // Service yet. - if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - final IntentFilter packageFilter = new IntentFilter(); - packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - packageFilter.addDataScheme(SCHEME_PACKAGE); - registerReceiver(mDictionaryPackInstallReceiver, packageFilter); + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme(SCHEME_PACKAGE); + registerReceiver(mDictionaryPackInstallReceiver, packageFilter); - final IntentFilter newDictFilter = new IntentFilter(); - newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); - registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); - } + final IntentFilter newDictFilter = new IntentFilter(); + newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); + registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); } // Has to be package-visible for unit tests @@ -480,8 +513,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final InputAttributes inputAttributes = new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode()); mSettings.loadSettings(locale, inputAttributes); - // May need to reset the contacts dictionary depending on the user settings. - resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); + AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(mSettings.getCurrent()); + // To load the keyboard we need to load all the settings once, but resetting the + // contacts dictionary should be deferred until after the new layout has been displayed + // to improve responsivity. In the language switching process, we post a reopenDictionaries + // message, then come here to read the settings for the new language before we change + // the layout; at this time, we need to skip resetting the contacts dictionary. It will + // be done later inside {@see #initSuggest()} when the reopenDictionaries message is + // processed. + if (!mHandler.hasPendingReopenDictionaries()) { + // May need to reset the contacts dictionary depending on the user settings. + resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); + } } // Note that this method is called from a non-UI thread. @@ -498,33 +541,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); final String localeStr = subtypeLocale.toString(); - final ContactsBinaryDictionary oldContactsDictionary; - if (mSuggest != null) { - oldContactsDictionary = mSuggest.getContactsDictionary(); - mSuggest.close(); - } else { - oldContactsDictionary = null; - } - mSuggest = new Suggest(this /* Context */, subtypeLocale, + final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale, this /* SuggestInitializationListener */); - if (mSettings.getCurrent().mCorrectionEnabled) { - mSuggest.setAutoCorrectionThreshold(mSettings.getCurrent().mAutoCorrectionThreshold); + final SettingsValues settingsValues = mSettings.getCurrent(); + if (settingsValues.mCorrectionEnabled) { + newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold); } mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().initSuggest(mSuggest); + ResearchLogger.getInstance().initSuggest(newSuggest); } mUserDictionary = new UserBinaryDictionary(this, localeStr); mIsUserDictionaryAvailable = mUserDictionary.isEnabled(); - mSuggest.setUserDictionary(mUserDictionary); - - resetContactsDictionary(oldContactsDictionary); + newSuggest.setUserDictionary(mUserDictionary); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - mUserHistoryDictionary = UserHistoryDictionary.getInstance(this, localeStr, prefs); - mSuggest.setUserHistoryDictionary(mUserHistoryDictionary); + + mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper + .getUserHistoryPredictionDictionary(this, localeStr, prefs); + newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary); + mPersonalizationPredictionDictionary = PersonalizationDictionaryHelper + .getPersonalizationPredictionDictionary(this, localeStr, prefs); + newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary); + + final Suggest oldSuggest = mSuggest; + resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null); + mSuggest = newSuggest; + if (oldSuggest != null) oldSuggest.close(); } /** @@ -536,8 +581,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * @param oldContactsDictionary an optional dictionary to use, or null */ private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) { + final Suggest suggest = mSuggest; final boolean shouldSetDictionary = - (null != mSuggest && mSettings.getCurrent().mUseContactsDict); + (null != suggest && mSettings.getCurrent().mUseContactsDict); final ContactsBinaryDictionary dictionaryToUse; if (!shouldSetDictionary) { @@ -564,8 +610,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - if (null != mSuggest) { - mSuggest.setContactsDictionary(dictionaryToUse); + if (null != suggest) { + suggest.setContactsDictionary(dictionaryToUse); } } @@ -577,8 +623,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onDestroy() { - if (mSuggest != null) { - mSuggest.close(); + final Suggest suggest = mSuggest; + if (suggest != null) { + suggest.close(); mSuggest = null; } mSettings.onDestroy(); @@ -586,11 +633,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.getInstance().onDestroy(); } - // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack - // Service yet. - if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - unregisterReceiver(mDictionaryPackInstallReceiver); - } + unregisterReceiver(mDictionaryPackInstallReceiver); LatinImeLogger.commit(); LatinImeLogger.onDestroy(); super.onDestroy(); @@ -676,7 +719,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen super.onStartInputView(editorInfo, restarting); final KeyboardSwitcher switcher = mKeyboardSwitcher; final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView(); - final SettingsValues currentSettings = mSettings.getCurrent(); + // If we are starting input in a different text field from before, we'll have to reload + // settings, so currentSettingsValues can't be final. + SettingsValues currentSettingsValues = mSettings.getCurrent(); if (editorInfo == null) { Log.e(TAG, "Null EditorInfo in onStartInputView()"); @@ -731,7 +776,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting); } - final boolean inputTypeChanged = !currentSettings.isSameInputType(editorInfo); + final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo); final boolean isDifferentTextField = !restarting || inputTypeChanged; if (isDifferentTextField) { mSubtypeSwitcher.updateParametersOnStartInputView(); @@ -746,6 +791,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // span, so we should reset our state unconditionally, even if restarting is true. mEnteredText = null; resetComposingState(true /* alsoResetLastComposedWord */); + if (isDifferentTextField) mHandler.postResumeSuggestions(); mDeleteCount = 0; mSpaceState = SPACE_STATE_NONE; mRecapitalizeStatus.deactivate(); @@ -753,7 +799,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Note: the following does a round-trip IPC on the main thread: be careful final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); - if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) { + final Suggest suggest = mSuggest; + if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) { initSuggest(); } if (mSuggestionStripView != null) { @@ -769,12 +816,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (isDifferentTextField) { mainKeyboardView.closing(); loadSettings(); + currentSettingsValues = mSettings.getCurrent(); - if (mSuggest != null && currentSettings.mCorrectionEnabled) { - mSuggest.setAutoCorrectionThreshold(currentSettings.mAutoCorrectionThreshold); + if (suggest != null && currentSettingsValues.mCorrectionEnabled) { + suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); } - switcher.loadKeyboard(editorInfo, currentSettings); + switcher.loadKeyboard(editorInfo, currentSettingsValues); } else if (restarting) { // TODO: Come up with a more comprehensive way to reset the keyboard layout when // a keyboard layout set doesn't get reloaded in this method. @@ -795,14 +843,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.cancelDoubleSpacePeriodTimer(); mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable); - mainKeyboardView.setKeyPreviewPopupEnabled(currentSettings.mKeyPreviewPopupOn, - currentSettings.mKeyPreviewPopupDismissDelay); + mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, + currentSettingsValues.mKeyPreviewPopupDismissDelay); mainKeyboardView.setSlidingKeyInputPreviewEnabled( - currentSettings.mSlidingKeyInputPreviewEnabled); + currentSettingsValues.mSlidingKeyInputPreviewEnabled); mainKeyboardView.setGestureHandlingEnabledByUser( - currentSettings.mGestureInputEnabled); - mainKeyboardView.setGesturePreviewMode(currentSettings.mGesturePreviewTrailEnabled, - currentSettings.mGestureFloatingPreviewTextEnabled); + currentSettingsValues.mGestureInputEnabled); + mainKeyboardView.setGesturePreviewMode(currentSettingsValues.mGesturePreviewTrailEnabled, + currentSettingsValues.mGestureFloatingPreviewTextEnabled); // If we have a user dictionary addition in progress, we should check now if we should // replace the previously committed string with the word that has actually been added @@ -850,11 +898,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mKeyboardSwitcher.onFinishInputView(); final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (mainKeyboardView != null) { - mainKeyboardView.cancelAllMessages(); + mainKeyboardView.cancelAllOngoingEvents(); + mainKeyboardView.deallocateMemory(); } // Remove pending messages related to update suggestions mHandler.cancelUpdateSuggestionStrip(); + // Should do the following in onFinishInputInternal but until JB MR2 it's not called :( + if (mWordComposer.isComposingWord()) mConnection.finishComposingText(); resetComposingState(true /* alsoResetLastComposedWord */); + mRichImm.clearSubtypeCaches(); // Notify ResearchLogger if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart, @@ -891,20 +943,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - // TODO: refactor the following code to be less contrived. - // "newSelStart != composingSpanEnd" || "newSelEnd != composingSpanEnd" means - // that the cursor is not at the end of the composing span, or there is a selection. - // "mLastSelectionStart != newSelStart" means that the cursor is not in the same place - // as last time we were called (if there is a selection, it means the start hasn't - // changed, so it's the end that did). - final boolean selectionChanged = (newSelStart != composingSpanEnd - || newSelEnd != composingSpanEnd) && mLastSelectionStart != newSelStart; + final boolean selectionChanged = mLastSelectionStart != newSelStart + || mLastSelectionEnd != newSelEnd; + // if composingSpanStart and composingSpanEnd are -1, it means there is no composing // span in the view - we can use that to narrow down whether the cursor was moved // by us or not. If we are composing a word but there is no composing span, then // we know for sure the cursor moved while we were composing and we should reset - // the state. + // the state. TODO: rescind this policy: the framework never removes the composing + // span on its own accord while editing. This test is useless. final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1; + // If the keyboard is not visible, we don't need to do all the housekeeping work, as it // will be reset when the keyboard shows up anyway. // TODO: revisit this when LatinIME supports hardware keyboards. @@ -926,7 +975,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // state-related special processing to kick in. mSpaceState = SPACE_STATE_NONE; - if ((!mWordComposer.isComposingWord()) || selectionChanged || noComposingSpan) { + // TODO: is it still necessary to test for composingSpan related stuff? + final boolean selectionChangedOrSafeToReset = selectionChanged + || (!mWordComposer.isComposingWord()) || noComposingSpan; + final boolean hasOrHadSelection = (oldSelStart != oldSelEnd + || newSelStart != newSelEnd); + final int moveAmount = newSelStart - oldSelStart; + if (selectionChangedOrSafeToReset && (hasOrHadSelection + || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { // If we are composing a word and moving the cursor, we would want to set a // suggestion span for recorrection to work correctly. Unfortunately, that // would involve the keyboard committing some new text, which would move the @@ -937,6 +993,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // text, but that is probably too expensive to do, so we decided to leave things // as is. resetEntireInputState(newSelStart); + } else { + // resetEntireInputState calls resetCachesUponCursorMove, but with the second + // argument as true. But in all cases where we don't reset the entire input state, + // we still want to tell the rich input connection about the new cursor position so + // that it can update its caches. + mConnection.resetCachesUponCursorMove(newSelStart, + false /* shouldFinishComposition */); } // We moved the cursor. If we are touching a word, we need to resume suggestion, @@ -1160,10 +1223,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void resetEntireInputState(final int newCursorPosition) { final boolean shouldFinishComposition = mWordComposer.isComposingWord(); resetComposingState(true /* alsoResetLastComposedWord */); - if (mSettings.getCurrent().mBigramPredictionEnabled) { + final SettingsValues settingsValues = mSettings.getCurrent(); + if (settingsValues.mBigramPredictionEnabled) { clearSuggestionStrip(); } else { - setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false); + setSuggestedWords(settingsValues.mSuggestPuncList, false); } mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition); } @@ -1237,8 +1301,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private boolean maybeDoubleSpacePeriod() { - if (!mSettings.getCurrent().mCorrectionEnabled) return false; - if (!mSettings.getCurrent().mUseDoubleSpacePeriod) return false; + final SettingsValues settingsValues = mSettings.getCurrent(); + if (!settingsValues.mCorrectionEnabled) return false; + if (!settingsValues.mUseDoubleSpacePeriod) return false; if (!mHandler.isAcceptingDoubleSpacePeriod()) return false; final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0); if (lastThree != null && lastThree.length() == 3 @@ -1314,14 +1379,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen showSubtypeSelectorAndSettings(); } - // Virtual codes representing custom requests. These are used in onCustomRequest() below. - public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1; - @Override public boolean onCustomRequest(final int requestCode) { if (isShowingOptionDialog()) return false; switch (requestCode) { - case CODE_SHOW_INPUT_METHOD_PICKER: + case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER: if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) { mRichImm.getInputMethodManager().showInputMethodPicker(); return true; @@ -1418,8 +1480,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen LatinImeLogger.logOnDelete(x, y); break; case Constants.CODE_SHIFT: - // Note: calling back to the keyboard on Shift key is handled in onPressKey() - // and onReleaseKey(). + // Note: Calling back to the keyboard on Shift key is handled in + // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}. final Keyboard currentKeyboard = switcher.getKeyboard(); if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) { // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for @@ -1427,9 +1489,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen handleRecapitalize(); } break; + case Constants.CODE_CAPSLOCK: + // Note: Changing keyboard to shift lock state is handled in + // {@link KeyboardSwitcher#onCodeInput(int)}. + break; case Constants.CODE_SWITCH_ALPHA_SYMBOL: - // Note: calling back to the keyboard on symbol key is handled in onPressKey() - // and onReleaseKey(). + // Note: Calling back to the keyboard on symbol key is handled in + // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}. break; case Constants.CODE_SETTINGS: onSettingsKeyPressed(); @@ -1482,8 +1548,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen break; } switcher.onCodeInput(primaryCode); - // Reset after any single keystroke, except shift and symbol-shift + // Reset after any single keystroke, except shift, capslock, and symbol-shift if (!didAutoCorrect && primaryCode != Constants.CODE_SHIFT + && primaryCode != Constants.CODE_CAPSLOCK && primaryCode != Constants.CODE_SWITCH_ALPHA_SYMBOL) mLastComposedWord.deactivate(); if (Constants.CODE_DELETE != primaryCode) { @@ -1496,14 +1563,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int spaceState) { mSpaceState = SPACE_STATE_NONE; final boolean didAutoCorrect; - if (mSettings.getCurrent().isWordSeparator(primaryCode)) { + final SettingsValues settingsValues = mSettings.getCurrent(); + if (settingsValues.isWordSeparator(primaryCode)) { didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState); } else { didAutoCorrect = false; if (SPACE_STATE_PHANTOM == spaceState) { - if (mSettings.isInternal()) { + if (settingsValues.mIsInternal) { if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) { - Stats.onAutoCorrection( + LatinImeLoggerUtils.onAutoCorrection( "", mWordComposer.getTypedWord(), " ", mWordComposer); } } @@ -1561,10 +1629,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen BatchInputUpdater.getInstance().onStartBatchInput(this); mHandler.cancelUpdateSuggestionStrip(); mConnection.beginBatchEdit(); + final SettingsValues settingsValues = mSettings.getCurrent(); if (mWordComposer.isComposingWord()) { - if (mSettings.isInternal()) { + if (settingsValues.mIsInternal) { if (mWordComposer.isBatchMode()) { - Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer); + LatinImeLoggerUtils.onAutoCorrection( + "", mWordComposer.getTypedWord(), " ", mWordComposer); } } final int wordComposerSize = mWordComposer.size(); @@ -1590,7 +1660,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); if (Character.isLetterOrDigit(codePointBeforeCursor) - || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) { + || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) { mSpaceState = SPACE_STATE_PHANTOM; } mConnection.endBatchEdit(); @@ -1635,8 +1705,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void onStartBatchInput(final LatinIME latinIme) { synchronized (mLock) { mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP); - mLatinIme = latinIme; mInBatchInput = true; + mLatinIme = latinIme; + mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip( + SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */); } } @@ -1795,8 +1867,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { final String word = mWordComposer.getTypedWord(); ResearchLogger.latinIME_handleBackspace_batch(word, 1); - ResearchLogger.getInstance().uncommitCurrentLogUnit( - word, false /* dumpCurrentLogUnit */); } final String rejectedSuggestion = mWordComposer.getTypedWord(); mWordComposer.reset(); @@ -1810,9 +1880,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mConnection.deleteSurroundingText(1, 0); } } else { + final SettingsValues currentSettings = mSettings.getCurrent(); if (mLastComposedWord.canRevertCommit()) { - if (mSettings.isInternal()) { - Stats.onAutoCorrectionCancellation(); + if (currentSettings.mIsInternal) { + LatinImeLoggerUtils.onAutoCorrectionCancellation(); } revertCommit(); return; @@ -1823,6 +1894,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // like the smiley key or the .com key. final int length = mEnteredText.length(); mConnection.deleteSurroundingText(length, 0); + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { + ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText); + } mEnteredText = null; // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false. // In addition we know that spaceState is false, and that we should not be @@ -1856,7 +1930,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mLastSelectionEnd = mLastSelectionStart; mConnection.deleteSurroundingText(numCharsDeleted, 0); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(numCharsDeleted); + ResearchLogger.latinIME_handleBackspace(numCharsDeleted, + false /* shouldUncommitLogUnit */); } } else { // There is no selection, just delete one character. @@ -1874,16 +1949,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mConnection.deleteSurroundingText(1, 0); } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(1); + ResearchLogger.latinIME_handleBackspace(1, true /* shouldUncommitLogUnit */); } if (mDeleteCount > DELETE_ACCELERATE_AT) { mConnection.deleteSurroundingText(1, 0); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(1); + ResearchLogger.latinIME_handleBackspace(1, + true /* shouldUncommitLogUnit */); } } } - if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) { + if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) { restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(); } } @@ -1900,8 +1976,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState) && isFromSuggestionStrip) { - if (mSettings.getCurrent().isUsuallyPrecededBySpace(code)) return false; - if (mSettings.getCurrent().isUsuallyFollowedBySpace(code)) return true; + final SettingsValues currentSettings = mSettings.getCurrent(); + if (currentSettings.isUsuallyPrecededBySpace(code)) return false; + if (currentSettings.isUsuallyFollowedBySpace(code)) return true; mConnection.removeTrailingSpace(); } return false; @@ -1913,8 +1990,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead. // See onStartBatchInput() to see how to do it. - if (SPACE_STATE_PHANTOM == spaceState && - !mSettings.getCurrent().isWordConnector(primaryCode)) { + final SettingsValues currentSettings = mSettings.getCurrent(); + if (SPACE_STATE_PHANTOM == spaceState && !currentSettings.isWordConnector(primaryCode)) { if (isComposingWord) { // Sanity check throw new RuntimeException("Should not be composing here"); @@ -1932,9 +2009,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI // thread here. if (!isComposingWord && (isAlphabet(primaryCode) - || mSettings.getCurrent().isWordConnector(primaryCode)) - && mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation) && - !mConnection.isCursorTouchingWord(mSettings.getCurrent())) { + || currentSettings.isWordConnector(primaryCode)) + && currentSettings.isSuggestionsRequested(mDisplayOrientation) && + !mConnection.isCursorTouchingWord(currentSettings)) { // Reset entirely the composing state anyway, then start composing a new word unless // the character is a single quote. The idea here is, single quote is not a // separator and it should be treated as a normal character, except in the first @@ -1977,8 +2054,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint(); } mHandler.postUpdateSuggestionStrip(); - if (mSettings.isInternal()) { - Utils.Stats.onNonSeparator((char)primaryCode, x, y); + if (currentSettings.mIsInternal) { + LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y); } } @@ -1990,9 +2067,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final CharSequence selectedText = mConnection.getSelectedText(0 /* flags, 0 for no styles */); if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection + final SettingsValues currentSettings = mSettings.getCurrent(); mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd, - selectedText.toString(), mSettings.getCurrentLocale(), - mSettings.getWordSeparators()); + selectedText.toString(), currentSettings.mLocale, + currentSettings.mWordSeparators); // We trim leading and trailing whitespace. mRecapitalizeStatus.trim(); // Trimming the object may have changed the length of the string, and we need to @@ -2020,17 +2098,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Returns true if we did an autocorrection, false otherwise. private boolean handleSeparator(final int primaryCode, final int x, final int y, final int spaceState) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord()); - } boolean didAutoCorrect = false; if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the separator at the current cursor position. resetEntireInputState(mLastSelectionStart); } + final SettingsValues currentSettings = mSettings.getCurrent(); if (mWordComposer.isComposingWord()) { - if (mSettings.getCurrent().mCorrectionEnabled) { + if (currentSettings.mCorrectionEnabled) { // TODO: maybe cache Strings in an <String> sparse array or something commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1)); didAutoCorrect = true; @@ -2043,13 +2119,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen Constants.SUGGESTION_STRIP_COORDINATE == x); if (SPACE_STATE_PHANTOM == spaceState && - mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) { + currentSettings.isUsuallyPrecededBySpace(primaryCode)) { promotePhantomSpace(); } + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { + ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord()); + } sendKeyCodePoint(primaryCode); if (Constants.CODE_SPACE == primaryCode) { - if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) { + if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) { if (maybeDoubleSpacePeriod()) { mSpaceState = SPACE_STATE_DOUBLE; } else if (!isShowingPunctuationList()) { @@ -2064,7 +2143,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen swapSwapperAndSpace(); mSpaceState = SPACE_STATE_SWAP_PUNCTUATION; } else if (SPACE_STATE_PHANTOM == spaceState - && mSettings.getCurrent().isUsuallyFollowedBySpace(primaryCode)) { + && currentSettings.isUsuallyFollowedBySpace(primaryCode)) { // If we are in phantom space state, and the user presses a separator, we want to // stay in phantom space state so that the next keypress has a chance to add the // space. For example, if I type "Good dat", pick "day" from the suggestion strip @@ -2082,8 +2161,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // already displayed or not, so it's okay. setPunctuationSuggestions(); } - if (mSettings.isInternal()) { - Utils.Stats.onSeparator((char)primaryCode, x, y); + if (currentSettings.mIsInternal) { + LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y); } mKeyboardSwitcher.updateShiftState(); @@ -2115,17 +2194,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private boolean isSuggestionsStripVisible() { + final SettingsValues currentSettings = mSettings.getCurrent(); if (mSuggestionStripView == null) return false; if (mSuggestionStripView.isShowingAddToDictionaryHint()) return true; - if (null == mSettings.getCurrent()) + if (null == currentSettings) return false; - if (!mSettings.getCurrent().isSuggestionStripVisibleInOrientation(mDisplayOrientation)) + if (!currentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation)) return false; - if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) + if (currentSettings.isApplicationSpecifiedCompletionsOn()) return true; - return mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation); + return currentSettings.isSuggestionsRequested(mDisplayOrientation); } private void clearSuggestionStrip() { @@ -2158,10 +2238,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void updateSuggestionStrip() { mHandler.cancelUpdateSuggestionStrip(); + final SettingsValues currentSettings = mSettings.getCurrent(); // Check if we have a suggestion engine attached. if (mSuggest == null - || !mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) { + || !currentSettings.isSuggestionsRequested(mDisplayOrientation)) { if (mWordComposer.isComposingWord()) { Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not " + "requested!"); @@ -2169,7 +2250,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } - if (!mWordComposer.isComposingWord() && !mSettings.getCurrent().mBigramPredictionEnabled) { + if (!mWordComposer.isComposingWord() && !currentSettings.mBigramPredictionEnabled) { setPunctuationSuggestions(); return; } @@ -2182,19 +2263,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private SuggestedWords getSuggestedWords(final int sessionId) { final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - if (keyboard == null || mSuggest == null) { + final Suggest suggest = mSuggest; + if (keyboard == null || suggest == null) { return SuggestedWords.EMPTY; } // Get the word on which we should search the bigrams. If we are composing a word, it's // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we // should just skip whitespace if any, so 1. // TODO: this is slow (2-way IPC) - we should probably cache this instead. + final SettingsValues currentSettings = mSettings.getCurrent(); final String prevWord = - mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators, + mConnection.getNthPreviousWord(currentSettings.mWordSeparators, mWordComposer.isComposingWord() ? 2 : 1); - return mSuggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(), - mSettings.getBlockPotentiallyOffensive(), - mSettings.getCurrent().mCorrectionEnabled, sessionId); + return suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(), + currentSettings.mBlockPotentiallyOffensive, + currentSettings.mCorrectionEnabled, sessionId); } private SuggestedWords getSuggestedWordsOrOlderSuggestions(final int sessionId) { @@ -2272,7 +2355,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen + "is empty? Impossible! I must commit suicide."); } if (mSettings.isInternal()) { - Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer); + LatinImeLoggerUtils.onAutoCorrection( + typedWord, autoCorrection, separatorString, mWordComposer); } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { final SuggestedWords suggestedWords = mSuggestedWords; @@ -2319,18 +2403,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } mConnection.beginBatchEdit(); + final SettingsValues currentSettings = mSettings.getCurrent(); if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0 // In the batch input mode, a manually picked suggested word should just replace // the current batch input text and there is no need for a phantom space. && !mWordComposer.isBatchMode()) { final int firstChar = Character.codePointAt(suggestion, 0); - if (!mSettings.getCurrent().isWordSeparator(firstChar) - || mSettings.getCurrent().isUsuallyPrecededBySpace(firstChar)) { + if (!currentSettings.isWordSeparator(firstChar) + || currentSettings.isUsuallyPrecededBySpace(firstChar)) { promotePhantomSpace(); } } - if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn() + if (currentSettings.isApplicationSpecifiedCompletionsOn() && mApplicationSpecifiedCompletions != null && index >= 0 && index < mApplicationSpecifiedCompletions.length) { mSuggestedWords = SuggestedWords.EMPTY; @@ -2354,7 +2439,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen LastComposedWord.NOT_A_SEPARATOR); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, - mWordComposer.isBatchMode()); + mWordComposer.isBatchMode(), suggestionInfo.mScore, suggestionInfo.mKind, + suggestionInfo.mSourceDict); } mConnection.endBatchEdit(); // Don't allow cancellation of manual pick @@ -2367,18 +2453,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // AND it's in none of our current dictionaries (main, user or otherwise). // Please note that if mSuggest is null, it means that everything is off: suggestion // and correction, so we shouldn't try to show the hint + final Suggest suggest = mSuggest; final boolean showingAddToDictionaryHint = - SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind && mSuggest != null - // If the suggestion is not in the dictionary, the hint should be shown. - && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true); - - if (mSettings.isInternal()) { - Stats.onSeparator((char)Constants.CODE_SPACE, + (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind + || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) + && suggest != null + // If the suggestion is not in the dictionary, the hint should be shown. + && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true); + + if (currentSettings.mIsInternal) { + LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); } if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) { mSuggestionStripView.showAddToDictionaryHint( - suggestion, mSettings.getCurrent().mHintToSaveText); + suggestion, currentSettings.mHintToSaveText); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. mHandler.postUpdateSuggestionStrip(); @@ -2404,10 +2493,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void setPunctuationSuggestions() { - if (mSettings.getCurrent().mBigramPredictionEnabled) { + final SettingsValues currentSettings = mSettings.getCurrent(); + if (currentSettings.mBigramPredictionEnabled) { clearSuggestionStrip(); } else { - setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false); + setSuggestedWords(currentSettings.mSuggestPuncList, false); } setAutoCorrectionIndicator(false); setSuggestionStripShown(isSuggestionsStripVisible()); @@ -2415,21 +2505,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private String addToUserHistoryDictionary(final String suggestion) { if (TextUtils.isEmpty(suggestion)) return null; - if (mSuggest == null) return null; + final Suggest suggest = mSuggest; + if (suggest == null) return null; // If correction is not enabled, we don't add words to the user history dictionary. // That's to avoid unintended additions in some sensitive fields, or fields that // expect to receive non-words. - if (!mSettings.getCurrent().mCorrectionEnabled) return null; + final SettingsValues currentSettings = mSettings.getCurrent(); + if (!currentSettings.mCorrectionEnabled) return null; - final Suggest suggest = mSuggest; - final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary; - if (suggest == null || userHistoryDictionary == null) { - // Avoid concurrent issue - return null; - } - final String prevWord - = mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators, 2); + final UserHistoryPredictionDictionary userHistoryPredictionDictionary = + mUserHistoryPredictionDictionary; + if (userHistoryPredictionDictionary == null) return null; + + final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2); final String secondWord; if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) { secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale()); @@ -2438,10 +2527,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid". // We don't add words with 0-frequency (assuming they would be profanity etc.). - final int maxFreq = AutoCorrection.getMaxFrequency( + final int maxFreq = AutoCorrectionUtils.getMaxFrequency( suggest.getUnigramDictionaries(), suggestion); if (maxFreq == 0) return null; - userHistoryDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0); + userHistoryPredictionDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0); return prevWord; } @@ -2458,35 +2547,34 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mLastSelectionStart != mLastSelectionEnd) return; // If we don't know the cursor location, return. if (mLastSelectionStart < 0) return; - if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return; - final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(), + final SettingsValues currentSettings = mSettings.getCurrent(); + if (!mConnection.isCursorTouchingWord(currentSettings)) return; + final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators, 0 /* additionalPrecedingWordsCount */); if (null == range) return; // Happens if we don't have an input connection at all // If for some strange reason (editor bug or so) we measure the text before the cursor as // longer than what the entire text is supposed to be, the safe thing to do is bail out. - if (range.mCharsBefore > mLastSelectionStart) return; + final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor(); + if (numberOfCharsInWordBeforeCursor > mLastSelectionStart) return; final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); final String typedWord = range.mWord.toString(); - if (range.mWord instanceof SpannableString) { - final SpannableString spannableString = (SpannableString)range.mWord; - int i = 0; - for (Object object : spannableString.getSpans(0, spannableString.length(), - SuggestionSpan.class)) { - SuggestionSpan span = (SuggestionSpan)object; - for (String s : span.getSuggestions()) { - ++i; - if (!TextUtils.equals(s, typedWord)) { - suggestions.add(new SuggestedWordInfo(s, - SuggestionStripView.MAX_SUGGESTIONS - i, - SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED)); - } + int i = 0; + for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) { + for (final String s : span.getSuggestions()) { + ++i; + if (!TextUtils.equals(s, typedWord)) { + suggestions.add(new SuggestedWordInfo(s, + SuggestionStripView.MAX_SUGGESTIONS - i, + SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED)); } } } mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard()); - mWordComposer.setCursorPositionWithinWord(range.mCharsBefore); - mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore, - mLastSelectionEnd + range.mCharsAfter); + // TODO: this is in chars but the callee expects code points! + mWordComposer.setCursorPositionWithinWord(numberOfCharsInWordBeforeCursor); + mConnection.setComposingRegion( + mLastSelectionStart - numberOfCharsInWordBeforeCursor, + mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor()); final SuggestedWords suggestedWords; if (suggestions.isEmpty()) { // We come here if there weren't any suggestion spans on this word. We will try to @@ -2575,18 +2663,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } mConnection.deleteSurroundingText(deleteLength, 0); if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) { - mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord); + mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord); } mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1); if (mSettings.isInternal()) { - Stats.onSeparator(mLastComposedWord.mSeparatorString, + LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord, mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString); - ResearchLogger.getInstance().uncommitCurrentLogUnit(committedWord, - true /* dumpCurrentLogUnit */); } // Don't restart suggestion yet. We'll restart if the user deletes the // separator. @@ -2599,45 +2685,54 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen public void promotePhantomSpace() { if (mSettings.getCurrent().shouldInsertSpacesAutomatically() && !mConnection.textBeforeCursorLooksLikeURL()) { - sendKeyCodePoint(Constants.CODE_SPACE); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_promotePhantomSpace(); } + sendKeyCodePoint(Constants.CODE_SPACE); } } - // Used by the RingCharBuffer - public boolean isWordSeparator(final int code) { - return mSettings.getCurrent().isWordSeparator(code); - } - // TODO: Make this private // Outside LatinIME, only used by the {@link InputTestsBase} test suite. @UsedForTesting void loadKeyboard() { - // TODO: Why are we calling {@link #loadSettings()} and {@link #initSuggest()} in a - // different order than in {@link #onStartInputView}? - initSuggest(); + // Since we are switching languages, the most urgent thing is to let the keyboard graphics + // update. LoadKeyboard does that, but we need to wait for buffer flip for it to be on + // the screen. Anything we do right now will delay this, so wait until the next frame + // before we do the rest, like reopening dictionaries and updating suggestions. So we + // post a message. + mHandler.postReopenDictionaries(); loadSettings(); if (mKeyboardSwitcher.getMainKeyboardView() != null) { // Reload keyboard because the current language has been changed. mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent()); } - // Since we just changed languages, we should re-evaluate suggestions with whatever word - // we are currently composing. If we are not composing anything, we may want to display - // predictions or punctuation signs (which is done by the updateSuggestionStrip anyway). - mHandler.postUpdateSuggestionStrip(); } - // Callback called by PointerTracker through the KeyboardActionListener. This is called when a - // key is depressed; release matching call is onReleaseKey below. + private void hapticAndAudioFeedback(final int code, final boolean isRepeatKey) { + final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView(); + if (keyboardView != null && keyboardView.isInSlidingKeyInput()) { + // No need to feedback while sliding input. + return; + } + if (isRepeatKey && code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) { + // No need to feedback when repeating delete key will have no effect. + return; + } + AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, keyboardView); + } + + // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed; + // release matching call is {@link #onReleaseKey(int,boolean)} below. @Override - public void onPressKey(final int primaryCode, final boolean isSinglePointer) { + public void onPressKey(final int primaryCode, final boolean isRepeatKey, + final boolean isSinglePointer) { mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer); + hapticAndAudioFeedback(primaryCode, isRepeatKey); } - // Callback by PointerTracker through the KeyboardActionListener. This is called when a key - // is released; press matching call is onPressKey above. + // Callback of the {@link KeyboardActionListener}. This is called when a key is released; + // press matching call is {@link #onPressKey(int,boolean,boolean)} above. @Override public void onReleaseKey(final int primaryCode, final boolean withSliding) { mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding); @@ -2703,7 +2798,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { mSubtypeSwitcher.onNetworkStateChanged(intent); } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - mKeyboardSwitcher.onRingerModeChanged(); + AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged(); } } }; @@ -2734,7 +2829,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final CharSequence[] items = new CharSequence[] { // TODO: Should use new string "Select active input modes". getString(R.string.language_selection_title), - getString(Utils.getAcitivityTitleResId(this, SettingsActivity.class)), + getString(ApplicationUtils.getAcitivityTitleResId(this, SettingsActivity.class)), }; final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override @@ -2787,6 +2882,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null; } + // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME. + @UsedForTesting + /* package for test */ boolean isCurrentlyWaitingForMainDictionary() { + return mSuggest.isCurrentlyWaitingForMainDictionary(); + } + + // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME. + @UsedForTesting + /* package for test */ boolean hasMainDictionary() { + return mSuggest.hasMainDictionary(); + } + public void debugDumpStateAndCrashWithException(final String context) { final StringBuilder s = new StringBuilder(mAppWorkAroundsUtils.toString()); s.append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes) |