aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java8
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java1306
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java1190
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java54
4 files changed, 1491 insertions, 1067 deletions
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index c260434d5..202ac87ef 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -141,7 +141,7 @@ public final class Constants {
}
public static final int NOT_A_CODE = -1;
-
+ public static final int NOT_A_CURSOR_POSITION = -1;
public static final int NOT_A_COORDINATE = -1;
public static final int SUGGESTION_STRIP_COORDINATE = -2;
public static final int SPELL_CHECKER_COORDINATE = -3;
@@ -154,6 +154,11 @@ public final class Constants {
// Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
+ // Key events coming any faster than this are long-presses.
+ public static final int LONG_PRESS_MILLISECONDS = 200;
+ // How many continuous deletes at which to start deleting at a higher speed.
+ public static final int DELETE_ACCELERATE_AT = 20;
+
public static boolean isValidCoordinate(final int coordinate) {
// Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE},
// and {@link SPELL_CHECKER_COORDINATE}.
@@ -242,5 +247,4 @@ public final class Constants {
private Constants() {
// This utility class is not publicly instantiable.
}
-
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 67b570277..ab9b2e598 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -76,6 +76,8 @@ import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.inputlogic.InputLogic;
+import com.android.inputmethod.latin.inputlogic.SpaceState;
import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
@@ -115,15 +117,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
Suggest.SuggestInitializationListener {
private static final String TAG = LatinIME.class.getSimpleName();
private static final boolean TRACE = false;
- private static boolean DEBUG;
+ // TODO[IL]: Make this private
+ public static boolean DEBUG;
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;
-
private static final int PENDING_IMS_CALLBACK_DURATION = 800;
private static final int PERIOD_FOR_AUDIO_AND_HAPTIC_FEEDBACK_IN_KEY_REPEAT = 2;
@@ -137,79 +135,38 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
*/
private static final String SCHEME_PACKAGE = "package";
- private static final int SPACE_STATE_NONE = 0;
- // Double space: the state where the user pressed space twice quickly, which LatinIME
- // resolved as period-space. Undoing this converts the period to a space.
- private static final int SPACE_STATE_DOUBLE = 1;
- // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip
- // have just been swapped. Undoing this swaps them back; the space is still considered weak.
- private static final int SPACE_STATE_SWAP_PUNCTUATION = 2;
- // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak
- // spaces happen when the user presses space, accepting the current suggestion (whether
- // it's an auto-correction or not).
- private static final int SPACE_STATE_WEAK = 3;
- // Phantom space: a not-yet-inserted space that should get inserted on the next input,
- // character provided it's not a separator. If it's a separator, the phantom space is dropped.
- // Phantom spaces happen when a user chooses a word from the suggestion strip.
- private static final int SPACE_STATE_PHANTOM = 4;
-
- // Current space state of the input method. This can be any of the above constants.
- private int mSpaceState;
-
private final Settings mSettings;
+ private final InputLogic mInputLogic = new InputLogic(this);
private View mExtractArea;
private View mKeyPreviewBackingView;
private SuggestionStripView mSuggestionStripView;
- // Never null
- private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
- private Suggest mSuggest;
+
private CompletionInfo[] mApplicationSpecifiedCompletions;
- private AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils();
+ // TODO[IL]: Make this an AsyncResultHolder or a Future in SettingsValues
+ public AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils();
private RichInputMethodManager mRichImm;
@UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
private final SubtypeSwitcher mSubtypeSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
- // At start, create a default event interpreter that does nothing by passing it no decoder spec.
- // The event interpreter should never be null.
- private EventInterpreter mEventInterpreter = new EventInterpreter(this);
private boolean mIsMainDictionaryAvailable;
private UserBinaryDictionary mUserDictionary;
private boolean mIsUserDictionaryAvailable;
- private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
- private final WordComposer mWordComposer = new WordComposer();
- private final RichInputConnection mConnection = new RichInputConnection(this);
- private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
-
- // Keep track of the last selection range to decide if we need to show word alternatives
- private static final int NOT_A_CURSOR_POSITION = -1;
- private int mLastSelectionStart = NOT_A_CURSOR_POSITION;
- private int mLastSelectionEnd = NOT_A_CURSOR_POSITION;
-
- private int mDeleteCount;
- private long mLastKeyTime;
- private final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
// Personalization debugging params
private boolean mUseOnlyPersonalizationDictionaryForDebug = false;
private boolean mBoostPersonalizationDictionaryForDebug = false;
- // Member variables for remembering the current device orientation.
- private int mDisplayOrientation;
+ // Member variable for remembering the current device orientation.
+ // TODO[IL]: Move this to SettingsValues.
+ public int mDisplayOrientation;
// Object for reacting to adding/removing a dictionary pack.
private BroadcastReceiver mDictionaryPackInstallReceiver =
new DictionaryPackInstallBroadcastReceiver(this);
- // Keeps track of most recently inserted text (multi-character key) for reverting
- private String mEnteredText;
-
- // TODO: This boolean is persistent state and causes large side effects at unexpected times.
- // Find a way to remove it for readability.
- private boolean mIsAutoCorrectionIndicatorOn;
-
private AlertDialog mOptionsDialog;
private final boolean mIsHardwareAcceleratedDrawingEnabled;
@@ -547,7 +504,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
initSuggest();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mSuggest);
+ ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mInputLogic.mSuggest);
}
mDisplayOrientation = getResources().getConfiguration().orientation;
@@ -588,9 +545,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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() && mSuggest != null) {
+ if (!mHandler.hasPendingReopenDictionaries() && mInputLogic.mSuggest != null) {
// May need to reset dictionaries depending on the user settings.
- mSuggest.setAdditionalDictionaries(mSuggest /* oldSuggest */, mSettings.getCurrent());
+ mInputLogic.mSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
+ mSettings.getCurrent());
}
}
@@ -636,24 +594,26 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mUserDictionary = new UserBinaryDictionary(this, subtypeLocale);
mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
newSuggest.setUserDictionary(mUserDictionary);
- newSuggest.setAdditionalDictionaries(mSuggest /* oldSuggest */, mSettings.getCurrent());
- final Suggest oldSuggest = mSuggest;
- mSuggest = newSuggest;
+ newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
+ mSettings.getCurrent());
+ final Suggest oldSuggest = mInputLogic.mSuggest;
+ mInputLogic.mSuggest = newSuggest;
if (oldSuggest != null) oldSuggest.close();
}
/* package private */ void resetSuggestMainDict() {
final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
+ mInputLogic.mSuggest.resetMainDict(this, subtypeLocale,
+ this /* SuggestInitializationListener */);
mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
}
@Override
public void onDestroy() {
- final Suggest suggest = mSuggest;
+ final Suggest suggest = mInputLogic.mSuggest;
if (suggest != null) {
suggest.close();
- mSuggest = null;
+ mInputLogic.mSuggest = null;
}
mSettings.onDestroy();
unregisterReceiver(mReceiver);
@@ -676,10 +636,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mDisplayOrientation != conf.orientation) {
mDisplayOrientation = conf.orientation;
mHandler.startOrientationChanging();
- mConnection.beginBatchEdit();
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- mConnection.finishComposingText();
- mConnection.endBatchEdit();
+ mInputLogic.mConnection.beginBatchEdit();
+ mInputLogic.commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ mInputLogic.mConnection.finishComposingText();
+ mInputLogic.mConnection.endBatchEdit();
if (isShowingOptionDialog()) {
mOptionsDialog.dismiss();
}
@@ -823,16 +783,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// The app calling setText() has the effect of clearing the composing
// span, so we should reset our state unconditionally, even if restarting is true.
- mEnteredText = null;
- resetComposingState(true /* alsoResetLastComposedWord */);
- mDeleteCount = 0;
- mSpaceState = SPACE_STATE_NONE;
- mRecapitalizeStatus.deactivate();
- mCurrentlyPressedHardwareKeys.clear();
+ mInputLogic.mEnteredText = null;
+ mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */);
+ mInputLogic.mDeleteCount = 0;
+ mInputLogic.mSpaceState = SpaceState.NONE;
+ mInputLogic.mRecapitalizeStatus.deactivate();
+ mInputLogic.mCurrentlyPressedHardwareKeys.clear();
// Note: the following does a round-trip IPC on the main thread: be careful
final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
- final Suggest suggest = mSuggest;
+ final Suggest suggest = mInputLogic.mSuggest;
if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) {
initSuggest();
}
@@ -841,14 +801,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// otherwise it will clear the suggestion strip.
setPunctuationSuggestions();
}
- mSuggestedWords = SuggestedWords.EMPTY;
+ mInputLogic.mSuggestedWords = SuggestedWords.EMPTY;
// Sometimes, while rotating, for some reason the framework tells the app we are not
// connected to it and that means we can't refresh the cache. In this case, schedule a
// refresh later.
final boolean canReachInputConnection;
- if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart,
- editorInfo.initialSelEnd, false /* shouldFinishComposition */)) {
+ if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
+ editorInfo.initialSelStart, editorInfo.initialSelEnd,
+ false /* shouldFinishComposition */)) {
// We try resetting the caches up to 5 times before giving up.
mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
// mLastSelection{Start,End} are reset later in this method, don't need to do it here
@@ -888,8 +849,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setSuggestionStripShownInternal(
isSuggestionsStripVisible(), /* needsInputViewShown */ false);
- mLastSelectionStart = editorInfo.initialSelStart;
- mLastSelectionEnd = editorInfo.initialSelEnd;
+ mInputLogic.mLastSelectionStart = editorInfo.initialSelStart;
+ mInputLogic.mLastSelectionEnd = editorInfo.initialSelEnd;
// In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
// so we try using some heuristics to find out about these and fix them.
tryFixLyingCursorPosition();
@@ -922,26 +883,29 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* than it really is.
*/
private void tryFixLyingCursorPosition() {
- final CharSequence textBeforeCursor =
- mConnection.getTextBeforeCursor(Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
+ final CharSequence textBeforeCursor = mInputLogic.mConnection.getTextBeforeCursor(
+ Constants.EDITOR_CONTENTS_CACHE_SIZE, 0);
if (null == textBeforeCursor) {
- mLastSelectionStart = mLastSelectionEnd = NOT_A_CURSOR_POSITION;
+ mInputLogic.mLastSelectionStart = mInputLogic.mLastSelectionEnd =
+ Constants.NOT_A_CURSOR_POSITION;
} else {
final int textLength = textBeforeCursor.length();
- if (textLength > mLastSelectionStart
+ if (textLength > mInputLogic.mLastSelectionStart
|| (textLength < Constants.EDITOR_CONTENTS_CACHE_SIZE
- && mLastSelectionStart < Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
+ && mInputLogic.mLastSelectionStart <
+ Constants.EDITOR_CONTENTS_CACHE_SIZE)) {
// It should not be possible to have only one of those variables be
// NOT_A_CURSOR_POSITION, so if they are equal, either the selection is zero-sized
// (simple cursor, no selection) or there is no cursor/we don't know its pos
- final boolean wasEqual = mLastSelectionStart == mLastSelectionEnd;
- mLastSelectionStart = textLength;
+ final boolean wasEqual =
+ mInputLogic.mLastSelectionStart == mInputLogic.mLastSelectionEnd;
+ mInputLogic.mLastSelectionStart = textLength;
// We can't figure out the value of mLastSelectionEnd :(
// But at least if it's smaller than mLastSelectionStart something is wrong,
// and if they used to be equal we also don't want to make it look like there is a
// selection.
- if (wasEqual || mLastSelectionStart > mLastSelectionEnd) {
- mLastSelectionEnd = mLastSelectionStart;
+ if (wasEqual || mInputLogic.mLastSelectionStart > mInputLogic.mLastSelectionEnd) {
+ mInputLogic.mLastSelectionEnd = mInputLogic.mLastSelectionStart;
}
}
}
@@ -998,12 +962,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// 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 */);
+ if (mInputLogic.mWordComposer.isComposingWord()) {
+ mInputLogic.mConnection.finishComposingText();
+ }
+ mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */);
// Notify ResearchLogger
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart,
- mLastSelectionEnd, getCurrentInputConnection());
+ ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput,
+ mInputLogic.mLastSelectionStart,
+ mInputLogic.mLastSelectionEnd, getCurrentInputConnection());
}
}
@@ -1016,21 +983,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (DEBUG) {
Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
+ ", ose=" + oldSelEnd
- + ", lss=" + mLastSelectionStart
- + ", lse=" + mLastSelectionEnd
+ + ", lss=" + mInputLogic.mLastSelectionStart
+ + ", lse=" + mInputLogic.mLastSelectionEnd
+ ", nss=" + newSelStart
+ ", nse=" + newSelEnd
+ ", cs=" + composingSpanStart
+ ", ce=" + composingSpanEnd);
}
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd,
+ ResearchLogger.latinIME_onUpdateSelection(mInputLogic.mLastSelectionStart,
+ mInputLogic.mLastSelectionEnd,
oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
- composingSpanEnd, mConnection);
+ composingSpanEnd, mInputLogic.mConnection);
}
- final boolean selectionChanged = mLastSelectionStart != newSelStart
- || mLastSelectionEnd != newSelEnd;
+ final boolean selectionChanged = mInputLogic.mLastSelectionStart != newSelStart
+ || mInputLogic.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
@@ -1045,24 +1013,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: revisit this when LatinIME supports hardware keyboards.
// NOTE: the test harness subclasses LatinIME and overrides isInputViewShown().
// TODO: find a better way to simulate actual execution.
- if (isInputViewShown() && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart,
- oldSelEnd, newSelEnd)) {
+ if (isInputViewShown() && !mInputLogic.mConnection.isBelatedExpectedUpdate(oldSelStart,
+ newSelStart, oldSelEnd, newSelEnd)) {
// TODO: the following is probably better done in resetEntireInputState().
// it should only happen when the cursor moved, and the very purpose of the
// test below is to narrow down whether this happened or not. Likewise with
// the call to updateShiftState.
// We set this to NONE because after a cursor move, we don't want the space
// state-related special processing to kick in.
- mSpaceState = SPACE_STATE_NONE;
+ mInputLogic.mSpaceState = SpaceState.NONE;
// TODO: is it still necessary to test for composingSpan related stuff?
final boolean selectionChangedOrSafeToReset = selectionChanged
- || (!mWordComposer.isComposingWord()) || noComposingSpan;
+ || (!mInputLogic.mWordComposer.isComposingWord()) || noComposingSpan;
final boolean hasOrHadSelection = (oldSelStart != oldSelEnd
|| newSelStart != newSelEnd);
final int moveAmount = newSelStart - oldSelStart;
if (selectionChangedOrSafeToReset && (hasOrHadSelection
- || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
+ || !mInputLogic.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
@@ -1072,14 +1041,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Another option would be to send suggestions each time we set the composing
// text, but that is probably too expensive to do, so we decided to leave things
// as is.
- resetEntireInputState(newSelStart, newSelEnd);
+ mInputLogic.resetEntireInputState(mSettings.getCurrent(), newSelStart, newSelEnd);
} else {
// resetEntireInputState calls resetCachesUponCursorMove, but forcing the
// composition to end. 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.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
- false /* shouldFinishComposition */);
+ mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
+ newSelStart, newSelEnd, false /* shouldFinishComposition */);
}
// We moved the cursor. If we are touching a word, we need to resume suggestion,
@@ -1088,13 +1057,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.postResumeSuggestions();
}
// Reset the last recapitalization.
- mRecapitalizeStatus.deactivate();
+ mInputLogic.mRecapitalizeStatus.deactivate();
mKeyboardSwitcher.updateShiftState();
}
// Make a note of the cursor position
- mLastSelectionStart = newSelStart;
- mLastSelectionEnd = newSelEnd;
+ mInputLogic.mLastSelectionStart = newSelStart;
+ mInputLogic.mLastSelectionEnd = newSelEnd;
mSubtypeState.currentSubtypeUsed();
}
@@ -1299,42 +1268,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mKeyPreviewBackingView.setVisibility(isFullscreenMode() ? View.GONE : View.VISIBLE);
}
- // This will reset the whole input state to the starting state. It will clear
- // the composing word, reset the last composed word, tell the inputconnection about it.
- private void resetEntireInputState(final int newSelStart, final int newSelEnd) {
- final boolean shouldFinishComposition = mWordComposer.isComposingWord();
- resetComposingState(true /* alsoResetLastComposedWord */);
- final SettingsValues settingsValues = mSettings.getCurrent();
- if (settingsValues.mBigramPredictionEnabled) {
- clearSuggestionStrip();
- } else {
- setSuggestedWords(settingsValues.mSuggestPuncList, false);
- }
- mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
- shouldFinishComposition);
- }
-
- private void resetComposingState(final boolean alsoResetLastComposedWord) {
- mWordComposer.reset();
- if (alsoResetLastComposedWord) {
- mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
- }
- }
-
- private void commitTyped(final String separatorString) {
- if (!mWordComposer.isComposingWord()) return;
- final String typedWord = mWordComposer.getTypedWord();
- if (typedWord.length() > 0) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
- }
- commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
- separatorString);
- }
- }
-
// Called from the KeyboardSwitcher which needs to know auto caps state to display
// the right layout.
+ // TODO[IL]: Move this to InputLogic.
public int getCurrentAutoCapsState() {
final SettingsValues currentSettingsValues = mSettings.getCurrent();
if (!currentSettingsValues.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
@@ -1344,96 +1280,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int inputType = ei.inputType;
// Warning: this depends on mSpaceState, which may not be the most current value. If
// mSpaceState gets updated later, whoever called this may need to be told about it.
- return mConnection.getCursorCapsMode(inputType, currentSettingsValues,
- SPACE_STATE_PHANTOM == mSpaceState);
+ return mInputLogic.mConnection.getCursorCapsMode(inputType, currentSettingsValues,
+ SpaceState.PHANTOM == mInputLogic.mSpaceState);
}
public int getCurrentRecapitalizeState() {
- if (!mRecapitalizeStatus.isActive()
- || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ if (!mInputLogic.mRecapitalizeStatus.isActive()
+ || !mInputLogic.mRecapitalizeStatus.isSetAt(mInputLogic.mLastSelectionStart,
+ mInputLogic.mLastSelectionEnd)) {
// Not recapitalizing at the moment
return RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
}
- return mRecapitalizeStatus.getCurrentMode();
- }
-
- // Factor in auto-caps and manual caps and compute the current caps mode.
- private int getActualCapsMode() {
- final int keyboardShiftMode = mKeyboardSwitcher.getKeyboardShiftMode();
- if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) return keyboardShiftMode;
- final int auto = getCurrentAutoCapsState();
- if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
- return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
- }
- if (0 != auto) {
- return WordComposer.CAPS_MODE_AUTO_SHIFTED;
- }
- return WordComposer.CAPS_MODE_OFF;
- }
-
- private void swapSwapperAndSpace() {
- final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
- // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
- if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == Constants.CODE_SPACE) {
- mConnection.deleteSurroundingText(2, 0);
- final String text = lastTwo.charAt(1) + " ";
- mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
- }
- mKeyboardSwitcher.updateShiftState();
- }
- }
-
- private boolean maybeDoubleSpacePeriod() {
- final SettingsValues currentSettingsValues = mSettings.getCurrent();
- if (!currentSettingsValues.mUseDoubleSpacePeriod) return false;
- if (!mHandler.isAcceptingDoubleSpacePeriod()) return false;
- // We only do this when we see two spaces and an accepted code point before the cursor.
- // The code point may be a surrogate pair but the two spaces may not, so we need 4 chars.
- final CharSequence lastThree = mConnection.getTextBeforeCursor(4, 0);
- if (null == lastThree) return false;
- final int length = lastThree.length();
- if (length < 3) return false;
- if (lastThree.charAt(length - 1) != Constants.CODE_SPACE) return false;
- if (lastThree.charAt(length - 2) != Constants.CODE_SPACE) return false;
- // We know there are spaces in pos -1 and -2, and we have at least three chars.
- // If we have only three chars, isSurrogatePairs can't return true as charAt(1) is a space,
- // so this is fine.
- final int firstCodePoint =
- Character.isSurrogatePair(lastThree.charAt(0), lastThree.charAt(1)) ?
- Character.codePointAt(lastThree, 0) : lastThree.charAt(length - 3);
- if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
- mHandler.cancelDoubleSpacePeriodTimer();
- mConnection.deleteSurroundingText(2, 0);
- final String textToInsert = new String(
- new int[] { currentSettingsValues.mSentenceSeparator, Constants.CODE_SPACE },
- 0, 2);
- mConnection.commitText(textToInsert, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
- false /* isBatchMode */);
- }
- mWordComposer.discardPreviousWordForSuggestion();
- mKeyboardSwitcher.updateShiftState();
- return true;
- }
- return false;
- }
-
- private static boolean canBeFollowedByDoubleSpacePeriod(final int codePoint) {
- // TODO: Check again whether there really ain't a better way to check this.
- // TODO: This should probably be language-dependant...
- return Character.isLetterOrDigit(codePoint)
- || codePoint == Constants.CODE_SINGLE_QUOTE
- || codePoint == Constants.CODE_DOUBLE_QUOTE
- || codePoint == Constants.CODE_CLOSING_PARENTHESIS
- || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET
- || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET
- || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET
- || codePoint == Constants.CODE_PLUS
- || codePoint == Constants.CODE_PERCENT
- || Character.getType(codePoint) == Character.OTHER_SYMBOL;
+ return mInputLogic.mRecapitalizeStatus.getCurrentMode();
}
// Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is
@@ -1445,7 +1303,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
final String wordToEdit;
- if (CapsModeUtils.isAutoCapsMode(mLastComposedWord.mCapitalizedMode)) {
+ if (CapsModeUtils.isAutoCapsMode(mInputLogic.mLastComposedWord.mCapitalizedMode)) {
wordToEdit = word.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
} else {
wordToEdit = word;
@@ -1453,7 +1311,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mUserDictionary.addWordToUserDictionary(wordToEdit);
}
- private void onSettingsKeyPressed() {
+ // TODO[IL]: Rework the route through which this is called.
+ public void onSettingsKeyPressed() {
if (isShowingOptionDialog()) return;
showSubtypeSelectorAndSettings();
}
@@ -1476,12 +1335,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mOptionsDialog != null && mOptionsDialog.isShowing();
}
- private void performEditorAction(final int actionId) {
- mConnection.performEditorAction(actionId);
- }
-
// TODO: Revise the language switch key behavior to make it much smarter and more reasonable.
- private void handleLanguageSwitchKey() {
+ // TODO[IL]: Move a part of this to InputLogic and straighten out the interface for this.
+ public void handleLanguageSwitchKey() {
final IBinder token = getWindow().getWindow().getAttributes().token;
if (mSettings.getCurrent().mIncludesOtherImesInLanguageSwitchList) {
mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */);
@@ -1490,197 +1346,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSubtypeState.switchSubtype(token, mRichImm);
}
- private void sendDownUpKeyEvent(final int code) {
- final long eventTime = SystemClock.uptimeMillis();
- mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
- KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
- mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
- KeyEvent.ACTION_UP, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
- }
-
- private void sendKeyCodePoint(final int code) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_sendKeyCodePoint(code);
- }
- // TODO: Remove this special handling of digit letters.
- // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
- if (code >= '0' && code <= '9') {
- sendDownUpKeyEvent(code - '0' + KeyEvent.KEYCODE_0);
- return;
- }
-
- if (Constants.CODE_ENTER == code && mAppWorkAroundsUtils.isBeforeJellyBean()) {
- // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
- // a hardware keyboard event on pressing enter or delete. This is bad for many
- // reasons (there are race conditions with commits) but some applications are
- // relying on this behavior so we continue to support it for older apps.
- sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
- } else {
- mConnection.commitText(StringUtils.newSingleCodePointString(code), 1);
- }
- }
-
// Implementation of {@link KeyboardActionListener}.
@Override
public void onCodeInput(final int primaryCode, final int x, final int y) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
- }
- final long when = SystemClock.uptimeMillis();
- if (primaryCode != Constants.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
- mDeleteCount = 0;
- }
- mLastKeyTime = when;
- mConnection.beginBatchEdit();
- final KeyboardSwitcher switcher = mKeyboardSwitcher;
- // The space state depends only on the last character pressed and its own previous
- // state. Here, we revert the space state to neutral if the key is actually modifying
- // the input contents (any non-shift key), which is what we should do for
- // all inputs that do not result in a special state. Each character handling is then
- // free to override the state as they see fit.
- final int spaceState = mSpaceState;
- if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false;
-
- // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.
- if (primaryCode != Constants.CODE_SPACE) {
- mHandler.cancelDoubleSpacePeriodTimer();
- }
-
- boolean didAutoCorrect = false;
- switch (primaryCode) {
- case Constants.CODE_DELETE:
- mSpaceState = SPACE_STATE_NONE;
- handleBackspace(spaceState);
- LatinImeLogger.logOnDelete(x, y);
- break;
- case Constants.CODE_SHIFT:
- // Note: Calling back to the keyboard on Shift key is handled in
- // {@link #onPressKey(int,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
- // alphabetic shift and shift while in symbol layout.
- 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
- // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
- break;
- case Constants.CODE_SETTINGS:
- onSettingsKeyPressed();
- break;
- case Constants.CODE_SHORTCUT:
- mSubtypeSwitcher.switchToShortcutIME(this);
- break;
- case Constants.CODE_ACTION_NEXT:
- performEditorAction(EditorInfo.IME_ACTION_NEXT);
- break;
- case Constants.CODE_ACTION_PREVIOUS:
- performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
- break;
- case Constants.CODE_LANGUAGE_SWITCH:
- handleLanguageSwitchKey();
- break;
- case Constants.CODE_EMOJI:
- // Note: Switching emoji keyboard is being handled in
- // {@link KeyboardState#onCodeInput(int,int)}.
- break;
- case Constants.CODE_ENTER:
- final EditorInfo editorInfo = getCurrentInputEditorInfo();
- final int imeOptionsActionId =
- InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
- if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
- // Either we have an actionLabel and we should performEditorAction with actionId
- // regardless of its value.
- performEditorAction(editorInfo.actionId);
- } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
- // We didn't have an actionLabel, but we had another action to execute.
- // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
- // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
- // means there should be an action and the app didn't bother to set a specific
- // code for it - presumably it only handles one. It does not have to be treated
- // in any specific way: anything that is not IME_ACTION_NONE should be sent to
- // performEditorAction.
- performEditorAction(imeOptionsActionId);
- } else {
- // No action label, and the action from imeOptions is NONE: this is a regular
- // enter key that should input a carriage return.
- didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
- }
- break;
- case Constants.CODE_SHIFT_ENTER:
- didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
- break;
- default:
- didAutoCorrect = handleNonSpecialCharacter(primaryCode, x, y, spaceState);
- break;
- }
- switcher.onCodeInput(primaryCode);
- // 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) {
- mEnteredText = null;
- }
- mConnection.endBatchEdit();
- }
-
- private boolean handleNonSpecialCharacter(final int primaryCode, final int x, final int y,
- final int spaceState) {
- mSpaceState = SPACE_STATE_NONE;
- final boolean didAutoCorrect;
- final SettingsValues settingsValues = mSettings.getCurrent();
- if (settingsValues.isWordSeparator(primaryCode)
- || Character.getType(primaryCode) == Character.OTHER_SYMBOL) {
- didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
- } else {
- didAutoCorrect = false;
- if (SPACE_STATE_PHANTOM == spaceState) {
- if (settingsValues.mIsInternal) {
- if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
- LatinImeLoggerUtils.onAutoCorrection(
- "", mWordComposer.getTypedWord(), " ", mWordComposer);
- }
- }
- 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 character at the current cursor position.
- resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
- } else {
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- }
- }
- final int keyX, keyY;
- final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
- if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
- keyX = x;
- keyY = y;
- } else {
- keyX = Constants.NOT_A_COORDINATE;
- keyY = Constants.NOT_A_COORDINATE;
- }
- handleCharacter(primaryCode, keyX, keyY, spaceState);
- }
- return didAutoCorrect;
+ mInputLogic.onCodeInput(primaryCode, x, y, mHandler, mKeyboardSwitcher, mSubtypeSwitcher);
}
// Called from PointerTracker through the KeyboardActionListener interface
+ // TODO[IL]: Move this to InputLogic
@Override
public void onTextInput(final String rawText) {
- mConnection.beginBatchEdit();
- if (mWordComposer.isComposingWord()) {
- commitCurrentAutoCorrection(rawText);
+ mInputLogic.mConnection.beginBatchEdit();
+ if (mInputLogic.mWordComposer.isComposingWord()) {
+ mInputLogic.commitCurrentAutoCorrection(mSettings.getCurrent(), rawText, mHandler);
} else {
- resetComposingState(true /* alsoResetLastComposedWord */);
+ mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */);
}
mHandler.postUpdateSuggestionStrip();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
@@ -1689,40 +1369,42 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
final String text = specificTldProcessingOnTextInput(rawText);
- if (SPACE_STATE_PHANTOM == mSpaceState) {
- promotePhantomSpace();
+ if (SpaceState.PHANTOM == mInputLogic.mSpaceState) {
+ mInputLogic.promotePhantomSpace(mSettings.getCurrent());
}
- mConnection.commitText(text, 1);
+ mInputLogic.mConnection.commitText(text, 1);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
}
- mConnection.endBatchEdit();
+ mInputLogic.mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
- mSpaceState = SPACE_STATE_NONE;
+ mInputLogic.mSpaceState = SpaceState.NONE;
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT);
- mEnteredText = text;
+ mInputLogic.mEnteredText = text;
}
@Override
public void onStartBatchInput() {
mInputUpdater.onStartBatchInput();
mHandler.cancelUpdateSuggestionStrip();
- mConnection.beginBatchEdit();
+ mInputLogic.mConnection.beginBatchEdit();
final SettingsValues currentSettingsValues = mSettings.getCurrent();
- if (mWordComposer.isComposingWord()) {
+ if (mInputLogic.mWordComposer.isComposingWord()) {
if (currentSettingsValues.mIsInternal) {
- if (mWordComposer.isBatchMode()) {
- LatinImeLoggerUtils.onAutoCorrection(
- "", mWordComposer.getTypedWord(), " ", mWordComposer);
+ if (mInputLogic.mWordComposer.isBatchMode()) {
+ LatinImeLoggerUtils.onAutoCorrection("",
+ mInputLogic.mWordComposer.getTypedWord(), " ",
+ mInputLogic.mWordComposer);
}
}
- final int wordComposerSize = mWordComposer.size();
+ final int wordComposerSize = mInputLogic.mWordComposer.size();
// Since isComposingWord() is true, the size is at least 1.
- if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ if (mInputLogic.mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the batch input at the current cursor position.
- resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
+ mInputLogic.resetEntireInputState(currentSettingsValues,
+ mInputLogic.mLastSelectionStart, mInputLogic.mLastSelectionEnd);
} else if (wordComposerSize <= 1) {
// We auto-correct the previous (typed, not gestured) string iff it's one character
// long. The reason for this is, even in the middle of gesture typing, you'll still
@@ -1731,17 +1413,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// tapping probably is that the word you intend to type is not in the dictionary,
// so we do not attempt to correct, on the assumption that if that was a dictionary
// word, the user would probably have gestured instead.
- commitCurrentAutoCorrection(LastComposedWord.NOT_A_SEPARATOR);
+ mInputLogic.commitCurrentAutoCorrection(currentSettingsValues,
+ LastComposedWord.NOT_A_SEPARATOR, mHandler);
} else {
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ mInputLogic.commitTyped(LastComposedWord.NOT_A_SEPARATOR);
}
}
- final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+ final int codePointBeforeCursor = mInputLogic.mConnection.getCodePointBeforeCursor();
if (Character.isLetterOrDigit(codePointBeforeCursor)
|| currentSettingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
final boolean autoShiftHasBeenOverriden = mKeyboardSwitcher.getKeyboardShiftMode() !=
getCurrentAutoCapsState();
- mSpaceState = SPACE_STATE_PHANTOM;
+ mInputLogic.mSpaceState = SpaceState.PHANTOM;
if (!autoShiftHasBeenOverriden) {
// When we change the space state, we need to update the shift state of the
// keyboard unless it has been overridden manually. This is happening for example
@@ -1751,10 +1434,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mKeyboardSwitcher.updateShiftState();
}
}
- mConnection.endBatchEdit();
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(getActualCapsMode(),
+ mInputLogic.mConnection.endBatchEdit();
+ mInputLogic.mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ mInputLogic.getActualCapsMode(mKeyboardSwitcher),
// Prev word is 1st word before cursor
- getNthPreviousWordForSuggestion(currentSettingsValues, 1 /* nthPreviousWord */));
+ mInputLogic.getNthPreviousWordForSuggestion(currentSettingsValues,
+ 1 /* nthPreviousWord */));
}
static final class InputUpdater implements Handler.Callback {
@@ -1857,7 +1542,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// be synchronized.
private void getSuggestedWordsGestureLocked(final InputPointers batchPointers,
final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
- mLatinIme.mWordComposer.setBatchInputPointers(batchPointers);
+ mLatinIme.mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
mLatinIme.getSuggestedWordsOrOlderSuggestionsAsync(Suggest.SESSION_GESTURE,
sequenceNumber, new OnGetSuggestedWordsCallback() {
@Override
@@ -1916,19 +1601,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onUpdateBatchInput(final InputPointers batchPointers) {
if (mSettings.getCurrent().mPhraseGestureEnabled) {
- final SuggestedWordInfo candidate = mSuggestedWords.getAutoCommitCandidate();
+ final SuggestedWordInfo candidate =
+ mInputLogic.mSuggestedWords.getAutoCommitCandidate();
// If these suggested words have been generated with out of date input pointers, then
// we skip auto-commit (see comments above on the mSequenceNumber member).
- if (null != candidate && mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) {
+ if (null != candidate
+ && mInputLogic.mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) {
if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
final String[] commitParts = candidate.mWord.split(" ", 2);
batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
- promotePhantomSpace();
- mConnection.commitText(commitParts[0], 0);
- mSpaceState = SPACE_STATE_PHANTOM;
+ mInputLogic.promotePhantomSpace(mSettings.getCurrent());
+ mInputLogic.mConnection.commitText(commitParts[0], 0);
+ mInputLogic.mSpaceState = SpaceState.PHANTOM;
mKeyboardSwitcher.updateShiftState();
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- getActualCapsMode(), commitParts[0]);
+ mInputLogic.mWordComposer.
+ setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ mInputLogic.getActualCapsMode(mKeyboardSwitcher), commitParts[0]);
++mAutoCommitSequenceNumber;
}
}
@@ -1942,30 +1630,31 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (TextUtils.isEmpty(batchInputText)) {
return;
}
- mConnection.beginBatchEdit();
- if (SPACE_STATE_PHANTOM == mSpaceState) {
- promotePhantomSpace();
+ mInputLogic.mConnection.beginBatchEdit();
+ if (SpaceState.PHANTOM == mInputLogic.mSpaceState) {
+ mInputLogic.promotePhantomSpace(mSettings.getCurrent());
}
if (mSettings.getCurrent().mPhraseGestureEnabled) {
// Find the last space
final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1;
if (0 != indexOfLastSpace) {
- mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1);
+ mInputLogic.mConnection.commitText(batchInputText.substring(0, indexOfLastSpace),
+ 1);
showSuggestionStrip(suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture());
}
final String lastWord = batchInputText.substring(indexOfLastSpace);
- mWordComposer.setBatchInputWord(lastWord);
- mConnection.setComposingText(lastWord, 1);
+ mInputLogic.mWordComposer.setBatchInputWord(lastWord);
+ mInputLogic.mConnection.setComposingText(lastWord, 1);
} else {
- mWordComposer.setBatchInputWord(batchInputText);
- mConnection.setComposingText(batchInputText, 1);
+ mInputLogic.mWordComposer.setBatchInputWord(batchInputText);
+ mInputLogic.mConnection.setComposingText(batchInputText, 1);
}
- mConnection.endBatchEdit();
+ mInputLogic.mConnection.endBatchEdit();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
}
// Space state must be updated before calling updateShiftState
- mSpaceState = SPACE_STATE_PHANTOM;
+ mInputLogic.mSpaceState = SpaceState.PHANTOM;
mKeyboardSwitcher.updateShiftState();
}
@@ -1982,9 +1671,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
// We have a TLD (or something that looks like this): make sure we don't add
// a space even if currently in phantom mode.
- mSpaceState = SPACE_STATE_NONE;
+ mInputLogic.mSpaceState = SpaceState.NONE;
// TODO: use getCodePointBeforeCursor instead to improve performance and simplify the code
- final CharSequence lastOne = mConnection.getTextBeforeCursor(1, 0);
+ final CharSequence lastOne = mInputLogic.mConnection.getTextBeforeCursor(1, 0);
if (lastOne != null && lastOne.length() == 1
&& lastOne.charAt(0) == Constants.CODE_PERIOD) {
return text.substring(1);
@@ -2012,378 +1701,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputUpdater.onCancelBatchInput();
}
- private void handleBackspace(final int spaceState) {
- mDeleteCount++;
-
- // In many cases, we may have to put the keyboard in auto-shift state again. However
- // we want to wait a few milliseconds before doing it to avoid the keyboard flashing
- // during key repeat.
- mHandler.postUpdateShiftState();
-
- if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
- // If we are in the middle of a recorrection, we need to commit the recorrection
- // first so that we can remove the character at the current cursor position.
- resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
- // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
- }
- if (mWordComposer.isComposingWord()) {
- if (mWordComposer.isBatchMode()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final String word = mWordComposer.getTypedWord();
- ResearchLogger.latinIME_handleBackspace_batch(word, 1);
- }
- final String rejectedSuggestion = mWordComposer.getTypedWord();
- mWordComposer.reset();
- mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
- } else {
- mWordComposer.deleteLast();
- }
- mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
- mHandler.postUpdateSuggestionStrip();
- if (!mWordComposer.isComposingWord()) {
- // If we just removed the last character, auto-caps mode may have changed so we
- // need to re-evaluate.
- mKeyboardSwitcher.updateShiftState();
- }
- } else {
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (mLastComposedWord.canRevertCommit()) {
- if (currentSettings.mIsInternal) {
- LatinImeLoggerUtils.onAutoCorrectionCancellation();
- }
- revertCommit();
- return;
- }
- if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
- // Cancel multi-character input: remove the text we just entered.
- // This is triggered on backspace after a key that inputs multiple characters,
- // like the smiley key or the .com key.
- mConnection.deleteSurroundingText(mEnteredText.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
- // reverting any autocorrect at this point. So we can safely return.
- return;
- }
- if (SPACE_STATE_DOUBLE == spaceState) {
- mHandler.cancelDoubleSpacePeriodTimer();
- if (mConnection.revertDoubleSpacePeriod()) {
- // No need to reset mSpaceState, it has already be done (that's why we
- // receive it as a parameter)
- return;
- }
- } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
- if (mConnection.revertSwapPunctuation()) {
- // Likewise
- return;
- }
- }
-
- // No cancelling of commit/double space/swap: we have a regular backspace.
- // We should backspace one char and restart suggestion if at the end of a word.
- if (mLastSelectionStart != mLastSelectionEnd) {
- // If there is a selection, remove it.
- final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
- mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
- // Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to
- // happen, and if it's wrong, the next call to onUpdateSelection will correct it,
- // but we want to set it right away to avoid it being used with the wrong values
- // later (typically, in a subsequent press on backspace).
- mLastSelectionEnd = mLastSelectionStart;
- mConnection.deleteSurroundingText(numCharsDeleted, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
- false /* shouldUncommitLogUnit */);
- }
- } else {
- // There is no selection, just delete one character.
- if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
- // This should never happen.
- Log.e(TAG, "Backspace when we don't know the selection position");
- }
- if (mAppWorkAroundsUtils.isBeforeJellyBean() ||
- currentSettings.mInputAttributes.isTypeNull()) {
- // There are two possible reasons to send a key event: either the field has
- // type TYPE_NULL, in which case the keyboard should send events, or we are
- // running in backward compatibility mode. Before Jelly bean, the keyboard
- // would simulate a hardware keyboard event on pressing enter or delete. This
- // is bad for many reasons (there are race conditions with commits) but some
- // applications are relying on this behavior so we continue to support it for
- // older apps, so we retain this behavior if the app has target SDK < JellyBean.
- sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
- if (mDeleteCount > DELETE_ACCELERATE_AT) {
- sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
- }
- } else {
- final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
- if (codePointBeforeCursor == Constants.NOT_A_CODE) {
- // Nothing to delete before the cursor.
- return;
- }
- final int lengthToDelete =
- Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
- mConnection.deleteSurroundingText(lengthToDelete, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(lengthToDelete,
- true /* shouldUncommitLogUnit */);
- }
- if (mDeleteCount > DELETE_ACCELERATE_AT) {
- final int codePointBeforeCursorToDeleteAgain =
- mConnection.getCodePointBeforeCursor();
- if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
- final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
- codePointBeforeCursorToDeleteAgain) ? 2 : 1;
- mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
- true /* shouldUncommitLogUnit */);
- }
- }
- }
- }
- }
- if (currentSettings.isSuggestionsRequested(mDisplayOrientation)
- && currentSettings.mCurrentLanguageHasSpaces) {
- restartSuggestionsOnWordBeforeCursorIfAtEndOfWord();
- }
- // We just removed a character. We need to update the auto-caps state.
- mKeyboardSwitcher.updateShiftState();
- }
- }
-
- /*
- * Strip a trailing space if necessary and returns whether it's a swap weak space situation.
- */
- private boolean maybeStripSpace(final int code, final int spaceState,
- final boolean isFromSuggestionStrip) {
- if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
- mConnection.removeTrailingSpace();
- return false;
- }
- if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
- && isFromSuggestionStrip) {
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (currentSettings.isUsuallyPrecededBySpace(code)) return false;
- if (currentSettings.isUsuallyFollowedBySpace(code)) return true;
- mConnection.removeTrailingSpace();
- }
- return false;
- }
-
- private void handleCharacter(final int primaryCode, final int x, final int y,
- final int spaceState) {
- // TODO: refactor this method to stop flipping isComposingWord around all the time, and
- // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
- // which has the same name as other handle* methods but is not the same.
- boolean isComposingWord = mWordComposer.isComposingWord();
-
- // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
- // See onStartBatchInput() to see how to do it.
- 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");
- }
- promotePhantomSpace();
- }
-
- 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 character at the current cursor position.
- resetEntireInputState(mLastSelectionStart, mLastSelectionEnd);
- isComposingWord = false;
- }
- // We want to find out whether to start composing a new word with this character. If so,
- // we need to reset the composing state and switch isComposingWord. The order of the
- // tests is important for good performance.
- // We only start composing if we're not already composing.
- if (!isComposingWord
- // We only start composing if this is a word code point. Essentially that means it's a
- // a letter or a word connector.
- && currentSettings.isWordCodePoint(primaryCode)
- // We never go into composing state if suggestions are not requested.
- && currentSettings.isSuggestionsRequested(mDisplayOrientation) &&
- // In languages with spaces, we only start composing a word when we are not already
- // touching a word. In languages without spaces, the above conditions are sufficient.
- (!mConnection.isCursorTouchingWord(currentSettings)
- || !currentSettings.mCurrentLanguageHasSpaces)) {
- // Reset entirely the composing state anyway, then start composing a new word unless
- // the character is a single quote or a dash. The idea here is, single quote and dash
- // are not separators and they should be treated as normal characters, except in the
- // first position where they should not start composing a word.
- isComposingWord = (Constants.CODE_SINGLE_QUOTE != primaryCode
- && Constants.CODE_DASH != primaryCode);
- // Here we don't need to reset the last composed word. It will be reset
- // when we commit this one, if we ever do; if on the other hand we backspace
- // it entirely and resume suggestions on the previous word, we'd like to still
- // have touch coordinates for it.
- resetComposingState(false /* alsoResetLastComposedWord */);
- }
- if (isComposingWord) {
- final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- // TODO: We should reconsider which coordinate system should be used to represent
- // keyboard event.
- final int keyX = mainKeyboardView.getKeyX(x);
- final int keyY = mainKeyboardView.getKeyY(y);
- mWordComposer.add(primaryCode, keyX, keyY);
- // If it's the first letter, make note of auto-caps state
- if (mWordComposer.size() == 1) {
- // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
- // yet, so the word we want is the 1st word before the cursor.
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- getActualCapsMode(),
- getNthPreviousWordForSuggestion(currentSettings, 1 /* nthPreviousWord */));
- }
- mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
- } else {
- final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
- Constants.SUGGESTION_STRIP_COORDINATE == x);
-
- sendKeyCodePoint(primaryCode);
-
- if (swapWeakSpace) {
- swapSwapperAndSpace();
- mSpaceState = SPACE_STATE_WEAK;
- }
- // In case the "add to dictionary" hint was still displayed.
- if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint();
- }
- mHandler.postUpdateSuggestionStrip();
- if (currentSettings.mIsInternal) {
- LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y);
- }
- }
-
- private void handleRecapitalize() {
- if (mLastSelectionStart == mLastSelectionEnd) return; // No selection
- // If we have a recapitalize in progress, use it; otherwise, create a new one.
- if (!mRecapitalizeStatus.isActive()
- || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
- 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(), 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
- // reposition the selection handles accordingly. As this result in an IPC call,
- // only do it if it's actually necessary, in other words if the recapitalize status
- // is not set at the same place as before.
- if (!mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
- mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
- mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
- }
- }
- mConnection.finishComposingText();
- mRecapitalizeStatus.rotate();
- final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
- mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
- mConnection.deleteSurroundingText(numCharsDeleted, 0);
- mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
- mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
- mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
- mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
- // Match the keyboard to the new state.
- mKeyboardSwitcher.updateShiftState();
- }
-
- // Returns true if we do an autocorrection, false otherwise.
- private boolean handleSeparator(final int primaryCode, final int x, final int y,
- final int spaceState) {
- boolean didAutoCorrect = false;
- final SettingsValues currentSettings = mSettings.getCurrent();
- // We avoid sending spaces in languages without spaces if we were composing.
- final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == primaryCode
- && !currentSettings.mCurrentLanguageHasSpaces && mWordComposer.isComposingWord();
- 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, mLastSelectionEnd);
- }
- if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing
- if (currentSettings.mCorrectionEnabled) {
- final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
- : StringUtils.newSingleCodePointString(primaryCode);
- commitCurrentAutoCorrection(separator);
- didAutoCorrect = true;
- } else {
- commitTyped(StringUtils.newSingleCodePointString(primaryCode));
- }
- }
-
- final boolean swapWeakSpace = maybeStripSpace(primaryCode, spaceState,
- Constants.SUGGESTION_STRIP_COORDINATE == x);
-
- if (SPACE_STATE_PHANTOM == spaceState &&
- currentSettings.isUsuallyPrecededBySpace(primaryCode)) {
- promotePhantomSpace();
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
- }
-
- if (!shouldAvoidSendingCode) {
- sendKeyCodePoint(primaryCode);
- }
-
- if (Constants.CODE_SPACE == primaryCode) {
- if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
- if (maybeDoubleSpacePeriod()) {
- mSpaceState = SPACE_STATE_DOUBLE;
- } else if (!isShowingPunctuationList()) {
- mSpaceState = SPACE_STATE_WEAK;
- }
- }
-
- mHandler.startDoubleSpacePeriodTimer();
- mHandler.postUpdateSuggestionStrip();
- } else {
- if (swapWeakSpace) {
- swapSwapperAndSpace();
- mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
- } else if (SPACE_STATE_PHANTOM == spaceState
- && 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
- // then insert a comma and go on to typing the next word, I want the space to be
- // inserted automatically before the next word, the same way it is when I don't
- // input the comma.
- // The case is a little different if the separator is a space stripper. Such a
- // separator does not normally need a space on the right (that's the difference
- // between swappers and strippers), so we should not stay in phantom space state if
- // the separator is a stripper. Hence the additional test above.
- mSpaceState = SPACE_STATE_PHANTOM;
- }
-
- // Set punctuation right away. onUpdateSelection will fire but tests whether it is
- // already displayed or not, so it's okay.
- setPunctuationSuggestions();
- }
- if (currentSettings.mIsInternal) {
- LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y);
- }
-
- mKeyboardSwitcher.updateShiftState();
- return didAutoCorrect;
- }
-
- private CharSequence getTextWithUnderline(final String text) {
- return mIsAutoCorrectionIndicatorOn
- ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
- : text;
- }
-
+ // TODO[IL]: Rename this to avoid using handle*
private void handleClose() {
// TODO: Verify that words are logged properly when IME is closed.
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ mInputLogic.commitTyped(LastComposedWord.NOT_A_SEPARATOR);
requestHideSelf(0);
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
@@ -2391,12 +1712,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- // TODO: make this private
+ // TODO[IL]: Move this to InputLogic and make it private
// Outside LatinIME, only used by the test suite.
@UsedForTesting
- boolean isShowingPunctuationList() {
- if (mSuggestedWords == null) return false;
- return mSettings.getCurrent().mSuggestPuncList == mSuggestedWords;
+ public boolean isShowingPunctuationList() {
+ if (mInputLogic.mSuggestedWords == null) return false;
+ return mSettings.getCurrent().mSuggestPuncList == mInputLogic.mSuggestedWords;
}
private boolean isSuggestionsStripVisible() {
@@ -2414,13 +1735,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return currentSettings.isSuggestionsRequested(mDisplayOrientation);
}
- private void clearSuggestionStrip() {
+ public void dismissAddToDictionaryHint() {
+ if (null != mSuggestionStripView) {
+ mSuggestionStripView.dismissAddToDictionaryHint();
+ }
+ }
+
+ // TODO[IL]: Define a clear interface for this
+ public void clearSuggestionStrip() {
setSuggestedWords(SuggestedWords.EMPTY, false);
setAutoCorrectionIndicator(false);
}
- private void setSuggestedWords(final SuggestedWords words, final boolean isAutoCorrection) {
- mSuggestedWords = words;
+ // TODO[IL]: Define a clear interface for this
+ public void setSuggestedWords(final SuggestedWords words, final boolean isAutoCorrection) {
+ mInputLogic.mSuggestedWords = words;
if (mSuggestionStripView != null) {
mSuggestionStripView.setSuggestions(words);
mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection);
@@ -2429,34 +1758,36 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void setAutoCorrectionIndicator(final boolean newAutoCorrectionIndicator) {
// Put a blue underline to a word in TextView which will be auto-corrected.
- if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
- && mWordComposer.isComposingWord()) {
- mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator;
+ if (mInputLogic.mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
+ && mInputLogic.mWordComposer.isComposingWord()) {
+ mInputLogic.mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator;
final CharSequence textWithUnderline =
- getTextWithUnderline(mWordComposer.getTypedWord());
+ mInputLogic.getTextWithUnderline(mInputLogic.mWordComposer.getTypedWord());
// TODO: when called from an updateSuggestionStrip() call that results from a posted
// message, this is called outside any batch edit. Potentially, this may result in some
// janky flickering of the screen, although the display speed makes it unlikely in
// the practice.
- mConnection.setComposingText(textWithUnderline, 1);
+ mInputLogic.mConnection.setComposingText(textWithUnderline, 1);
}
}
- private void updateSuggestionStrip() {
+ // TODO[IL]: Move this to InputLogic and make private again
+ public void updateSuggestionStrip() {
mHandler.cancelUpdateSuggestionStrip();
final SettingsValues currentSettings = mSettings.getCurrent();
// Check if we have a suggestion engine attached.
- if (mSuggest == null
+ if (mInputLogic.mSuggest == null
|| !currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
- if (mWordComposer.isComposingWord()) {
+ if (mInputLogic.mWordComposer.isComposingWord()) {
Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
+ "requested!");
}
return;
}
- if (!mWordComposer.isComposingWord() && !currentSettings.mBigramPredictionEnabled) {
+ if (!mInputLogic.mWordComposer.isComposingWord()
+ && !currentSettings.mBigramPredictionEnabled) {
setPunctuationSuggestions();
return;
}
@@ -2478,28 +1809,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
- /**
- * Get the nth previous word before the cursor as context for the suggestion process.
- * @param currentSettings the current settings values.
- * @param nthPreviousWord reverse index of the word to get (1-indexed)
- * @return the nth previous word before the cursor.
- */
- private String getNthPreviousWordForSuggestion(final SettingsValues currentSettings,
- final int nthPreviousWord) {
- if (currentSettings.mCurrentLanguageHasSpaces) {
- // If we are typing in a language with spaces we can just look up the previous
- // word from textview.
- return mConnection.getNthPreviousWord(currentSettings, nthPreviousWord);
- } else {
- return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
- : mLastComposedWord.mCommittedWord;
- }
- }
-
private void getSuggestedWords(final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
- final Suggest suggest = mSuggest;
+ final Suggest suggest = mInputLogic.mSuggest;
if (keyboard == null || suggest == null) {
callback.onGetSuggestedWords(SuggestedWords.EMPTY);
return;
@@ -2511,22 +1824,25 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
if (DEBUG) {
- if (mWordComposer.isComposingWord() || mWordComposer.isBatchMode()) {
- final String previousWord = mWordComposer.getPreviousWordForSuggestion();
+ if (mInputLogic.mWordComposer.isComposingWord()
+ || mInputLogic.mWordComposer.isBatchMode()) {
+ final String previousWord
+ = mInputLogic.mWordComposer.getPreviousWordForSuggestion();
// TODO: this is for checking consistency with older versions. Remove this when
// we are confident this is stable.
// We're checking the previous word in the text field against the memorized previous
// word. If we are composing a word we should have the second word before the cursor
// memorized, otherwise we should have the first.
- final String rereadPrevWord = getNthPreviousWordForSuggestion(currentSettings,
- mWordComposer.isComposingWord() ? 2 : 1);
+ final String rereadPrevWord = mInputLogic.getNthPreviousWordForSuggestion(
+ currentSettings, mInputLogic.mWordComposer.isComposingWord() ? 2 : 1);
if (!TextUtils.equals(previousWord, rereadPrevWord)) {
throw new RuntimeException("Unexpected previous word: "
+ previousWord + " <> " + rereadPrevWord);
}
}
}
- suggest.getSuggestedWords(mWordComposer, mWordComposer.getPreviousWordForSuggestion(),
+ suggest.getSuggestedWords(mInputLogic.mWordComposer,
+ mInputLogic.mWordComposer.getPreviousWordForSuggestion(),
keyboard.getProximityInfo(),
currentSettings.mBlockPotentiallyOffensive, currentSettings.mCorrectionEnabled,
additionalFeaturesOptions, sessionId, sequenceNumber, callback);
@@ -2539,7 +1855,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onGetSuggestedWords(SuggestedWords suggestedWords) {
callback.onGetSuggestedWords(maybeRetrieveOlderSuggestions(
- mWordComposer.getTypedWord(), suggestedWords));
+ mInputLogic.mWordComposer.getTypedWord(), suggestedWords));
}
});
}
@@ -2563,7 +1879,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private SuggestedWords getOlderSuggestions(final String typedWord) {
- SuggestedWords previousSuggestedWords = mSuggestedWords;
+ SuggestedWords previousSuggestedWords = mInputLogic.mSuggestedWords;
if (previousSuggestedWords == mSettings.getCurrent().mSuggestPuncList) {
previousSuggestedWords = SuggestedWords.EMPTY;
}
@@ -2591,7 +1907,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// because it may differ from mWordComposer.mTypedWord.
autoCorrection = typedWord;
}
- mWordComposer.setAutoCorrection(autoCorrection);
+ mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
}
private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
@@ -2621,50 +1937,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD));
}
- private void commitCurrentAutoCorrection(final String separator) {
- // Complete any pending suggestions query first
- if (mHandler.hasPendingUpdateSuggestions()) {
- updateSuggestionStrip();
- }
- final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
- final String typedWord = mWordComposer.getTypedWord();
- final String autoCorrection = (typedAutoCorrection != null)
- ? typedAutoCorrection : typedWord;
- if (autoCorrection != null) {
- if (TextUtils.isEmpty(typedWord)) {
- throw new RuntimeException("We have an auto-correction but the typed word "
- + "is empty? Impossible! I must commit suicide.");
- }
- if (mSettings.isInternal()) {
- LatinImeLoggerUtils.onAutoCorrection(
- typedWord, autoCorrection, separator, mWordComposer);
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final SuggestedWords suggestedWords = mSuggestedWords;
- ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
- separator, mWordComposer.isBatchMode(), suggestedWords);
- }
- commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
- separator);
- if (!typedWord.equals(autoCorrection)) {
- // This will make the correction flash for a short while as a visual clue
- // to the user that auto-correction happened. It has no other effect; in particular
- // note that this won't affect the text inside the text field AT ALL: it only makes
- // the segment of text starting at the supplied index and running for the length
- // of the auto-correction flash. At this moment, the "typedWord" argument is
- // ignored by TextView.
- mConnection.commitCorrection(
- new CorrectionInfo(mLastSelectionEnd - typedWord.length(),
- typedWord, autoCorrection));
- }
- }
- }
-
// Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
// interface
@Override
public void pickSuggestionManually(final int index, final SuggestedWordInfo suggestionInfo) {
- final SuggestedWords suggestedWords = mSuggestedWords;
+ final SuggestedWords suggestedWords = mInputLogic.mSuggestedWords;
final String suggestion = suggestionInfo.mWord;
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
if (suggestion.length() == 1 && isShowingPunctuationList()) {
@@ -2682,57 +1959,57 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
- mConnection.beginBatchEdit();
+ mInputLogic.mConnection.beginBatchEdit();
final SettingsValues currentSettings = mSettings.getCurrent();
- if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0
+ if (SpaceState.PHANTOM == mInputLogic.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()) {
+ && !mInputLogic.mWordComposer.isBatchMode()) {
final int firstChar = Character.codePointAt(suggestion, 0);
if (!currentSettings.isWordSeparator(firstChar)
|| currentSettings.isUsuallyPrecededBySpace(firstChar)) {
- promotePhantomSpace();
+ mInputLogic.promotePhantomSpace(currentSettings);
}
}
if (currentSettings.isApplicationSpecifiedCompletionsOn()
&& mApplicationSpecifiedCompletions != null
&& index >= 0 && index < mApplicationSpecifiedCompletions.length) {
- mSuggestedWords = SuggestedWords.EMPTY;
+ mInputLogic.mSuggestedWords = SuggestedWords.EMPTY;
if (mSuggestionStripView != null) {
mSuggestionStripView.clear();
}
mKeyboardSwitcher.updateShiftState();
- resetComposingState(true /* alsoResetLastComposedWord */);
+ mInputLogic.resetComposingState(true /* alsoResetLastComposedWord */);
final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
- mConnection.commitCompletion(completionInfo);
- mConnection.endBatchEdit();
+ mInputLogic.mConnection.commitCompletion(completionInfo);
+ mInputLogic.mConnection.endBatchEdit();
return;
}
// We need to log before we commit, because the word composer will store away the user
// typed word.
- final String replacedWord = mWordComposer.getTypedWord();
+ final String replacedWord = mInputLogic.mWordComposer.getTypedWord();
LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
- mWordComposer.isBatchMode(), suggestionInfo.mScore, suggestionInfo.mKind,
- suggestionInfo.mSourceDict.mDictType);
+ mInputLogic.mWordComposer.isBatchMode(), suggestionInfo.mScore,
+ suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType);
}
- mConnection.endBatchEdit();
+ mInputLogic.mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
- mLastComposedWord.deactivate();
+ mInputLogic.mLastComposedWord.deactivate();
// Space state must be updated before calling updateShiftState
- mSpaceState = SPACE_STATE_PHANTOM;
+ mInputLogic.mSpaceState = SpaceState.PHANTOM;
mKeyboardSwitcher.updateShiftState();
// We should show the "Touch again to save" hint if the user pressed the first entry
// 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 Suggest suggest = mInputLogic.mSuggest;
final boolean showingAddToDictionaryHint =
(SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
|| SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
@@ -2756,10 +2033,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
/**
* Commits the chosen word to the text field and saves it for later retrieval.
*/
- private void commitChosenWord(final String chosenWord, final int commitType,
+ // TODO[IL]: Move to InputLogic and make public again
+ public void commitChosenWord(final String chosenWord, final int commitType,
final String separatorString) {
- final SuggestedWords suggestedWords = mSuggestedWords;
- mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
+ final SuggestedWords suggestedWords = mInputLogic.mSuggestedWords;
+ mInputLogic.mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
// Add the word to the user history dictionary
final String prevWord = addToUserHistoryDictionary(chosenWord);
@@ -2767,8 +2045,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// what user typed. Note: currently this is done much later in
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
- mLastComposedWord = mWordComposer.commitWord(commitType, chosenWord, separatorString,
- prevWord);
+ mInputLogic.mLastComposedWord = mInputLogic.mWordComposer.commitWord(commitType,
+ chosenWord, separatorString, prevWord);
final boolean shouldDiscardPreviousWordForSuggestion;
if (0 == StringUtils.codePointCount(separatorString)) {
// Separator is 0-length. Discard the word only if the current language has spaces.
@@ -2780,11 +2058,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
!StringUtils.containsOnlyWhitespace(separatorString);
}
if (shouldDiscardPreviousWordForSuggestion) {
- mWordComposer.discardPreviousWordForSuggestion();
+ mInputLogic.mWordComposer.discardPreviousWordForSuggestion();
}
}
- private void setPunctuationSuggestions() {
+ // TODO[IL]: Define a clean interface for this
+ public void setPunctuationSuggestions() {
final SettingsValues currentSettings = mSettings.getCurrent();
if (currentSettings.mBigramPredictionEnabled) {
clearSuggestionStrip();
@@ -2797,7 +2076,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private String addToUserHistoryDictionary(final String suggestion) {
if (TextUtils.isEmpty(suggestion)) return null;
- final Suggest suggest = mSuggest;
+ final Suggest suggest = mInputLogic.mSuggest;
if (suggest == null) return null;
// If correction is not enabled, we don't add words to the user history dictionary.
@@ -2809,9 +2088,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final UserHistoryDictionary userHistoryDictionary = suggest.getUserHistoryDictionary();
if (userHistoryDictionary == null) return null;
- final String prevWord = mConnection.getNthPreviousWord(currentSettings, 2);
+ final String prevWord = mInputLogic.mConnection.getNthPreviousWord(currentSettings, 2);
final String secondWord;
- if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
+ if (mInputLogic.mWordComposer.wasAutoCapitalized()
+ && !mInputLogic.mWordComposer.isMostlyCaps()) {
secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
} else {
secondWord = suggestion;
@@ -2848,19 +2128,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// how to segment them yet.
if (!mSettings.getCurrent().mCurrentLanguageHasSpaces) return;
// If the cursor is not touching a word, or if there is a selection, return right away.
- if (mLastSelectionStart != mLastSelectionEnd) return;
+ if (mInputLogic.mLastSelectionStart != mInputLogic.mLastSelectionEnd) return;
// If we don't know the cursor location, return.
- if (mLastSelectionStart < 0) return;
+ if (mInputLogic.mLastSelectionStart < 0) return;
final SettingsValues currentSettings = mSettings.getCurrent();
- if (!mConnection.isCursorTouchingWord(currentSettings)) return;
- final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators,
- 0 /* additionalPrecedingWordsCount */);
+ if (!mInputLogic.mConnection.isCursorTouchingWord(currentSettings)) return;
+ final TextRange range = mInputLogic.mConnection.getWordRangeAtCursor(
+ currentSettings.mWordSeparators, 0 /* additionalPrecedingWordsCount */);
if (null == range) return; // Happens if we don't have an input connection at all
if (range.length() <= 0) return; // Race condition. No text to resume on, so bail out.
// 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.
final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
- if (numberOfCharsInWordBeforeCursor > mLastSelectionStart) return;
+ if (numberOfCharsInWordBeforeCursor > mInputLogic.mLastSelectionStart) return;
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
final String typedWord = range.mWord.toString();
if (!isResumableWord(typedWord, currentSettings)) return;
@@ -2878,18 +2158,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
}
}
- mWordComposer.setComposingWord(typedWord,
- getNthPreviousWordForSuggestion(currentSettings,
+ mInputLogic.mWordComposer.setComposingWord(typedWord,
+ mInputLogic.getNthPreviousWordForSuggestion(currentSettings,
// We want the previous word for suggestion. If we have chars in the word
// before the cursor, then we want the word before that, hence 2; otherwise,
// we want the word immediately before the cursor, hence 1.
0 == numberOfCharsInWordBeforeCursor ? 1 : 2),
mKeyboardSwitcher.getKeyboard());
- mWordComposer.setCursorPositionWithinWord(
+ mInputLogic.mWordComposer.setCursorPositionWithinWord(
typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
- mConnection.setComposingRegion(
- mLastSelectionStart - numberOfCharsInWordBeforeCursor,
- mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor());
+ mInputLogic.mConnection.setComposingRegion(
+ mInputLogic.mLastSelectionStart - numberOfCharsInWordBeforeCursor,
+ mInputLogic.mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor());
if (suggestions.isEmpty()) {
// We come here if there weren't any suggestion spans on this word. We will try to
// compute suggestions for it instead.
@@ -2938,42 +2218,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// We also need to unset mIsAutoCorrectionIndicatorOn to avoid showSuggestionStrip touching
// the text to adapt it.
// TODO: remove mIsAutoCorrectionIndicatorOn (see comment on definition)
- mIsAutoCorrectionIndicatorOn = false;
+ mInputLogic.mIsAutoCorrectionIndicatorOn = false;
mHandler.showSuggestionStripWithTypedWord(suggestedWords, typedWord);
}
/**
- * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
- * word, else do nothing.
- */
- private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() {
- final CharSequence word =
- mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
- if (null != word) {
- final String wordString = word.toString();
- restartSuggestionsOnWordBeforeCursor(wordString);
- // TODO: Handle the case where the user manually moves the cursor and then backs up over
- // a separator. In that case, the current log unit should not be uncommitted.
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString,
- true /* dumpCurrentLogUnit */);
- }
- }
- }
-
- private void restartSuggestionsOnWordBeforeCursor(final String word) {
- mWordComposer.setComposingWord(word,
- // Previous word is the 2nd word before cursor because we are restarting on the
- // 1st word before cursor.
- getNthPreviousWordForSuggestion(mSettings.getCurrent(), 2 /* nthPreviousWord */),
- mKeyboardSwitcher.getKeyboard());
- final int length = word.length();
- mConnection.deleteSurroundingText(length, 0);
- mConnection.setComposingText(word, 1);
- mHandler.postUpdateSuggestionStrip();
- }
-
- /**
* Retry resetting caches in the rich input connection.
*
* When the editor can't be accessed we can't reset the caches, so we schedule a retry.
@@ -2984,8 +2233,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* @param remainingTries How many times we may try again before giving up.
*/
private void retryResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
- if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart,
- mLastSelectionEnd, false)) {
+ if (!mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess(
+ mInputLogic.mLastSelectionStart, mInputLogic.mLastSelectionEnd, false)) {
if (0 < remainingTries) {
mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
return;
@@ -2998,74 +2247,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (tryResumeSuggestions) mHandler.postResumeSuggestions();
}
- private void revertCommit() {
- final String previousWord = mLastComposedWord.mPrevWord;
- final String originallyTypedWord = mLastComposedWord.mTypedWord;
- final String committedWord = mLastComposedWord.mCommittedWord;
- final int cancelLength = committedWord.length();
- // We want java chars, not codepoints for the following.
- final int separatorLength = mLastComposedWord.mSeparatorString.length();
- // TODO: should we check our saved separator against the actual contents of the text view?
- final int deleteLength = cancelLength + separatorLength;
- if (DEBUG) {
- if (mWordComposer.isComposingWord()) {
- throw new RuntimeException("revertCommit, but we are composing a word");
- }
- final CharSequence wordBeforeCursor =
- mConnection.getTextBeforeCursor(deleteLength, 0).subSequence(0, cancelLength);
- if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
- throw new RuntimeException("revertCommit check failed: we thought we were "
- + "reverting \"" + committedWord
- + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
- }
- }
- mConnection.deleteSurroundingText(deleteLength, 0);
- if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
- if (mSuggest != null) {
- mSuggest.cancelAddingUserHistory(previousWord, committedWord);
- }
- }
- final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
- if (mSettings.getCurrent().mCurrentLanguageHasSpaces) {
- // For languages with spaces, we revert to the typed string, but the cursor is still
- // after the separator so we don't resume suggestions. If the user wants to correct
- // the word, they have to press backspace again.
- mConnection.commitText(stringToCommit, 1);
- } else {
- // For languages without spaces, we revert the typed string but the cursor is flush
- // with the typed word, so we need to resume suggestions right away.
- mWordComposer.setComposingWord(stringToCommit, previousWord,
- mKeyboardSwitcher.getKeyboard());
- mConnection.setComposingText(stringToCommit, 1);
- }
- if (mSettings.isInternal()) {
- 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);
- }
- // Don't restart suggestion yet. We'll restart if the user deletes the
- // separator.
- mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
- // We have a separator between the word and the cursor: we should show predictions.
- mHandler.postUpdateSuggestionStrip();
- }
-
- // This essentially inserts a space, and that's it.
- public void promotePhantomSpace() {
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (currentSettings.shouldInsertSpacesAutomatically()
- && currentSettings.mCurrentLanguageHasSpaces
- && !mConnection.textBeforeCursorLooksLikeURL()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_promotePhantomSpace();
- }
- sendKeyCodePoint(Constants.CODE_SPACE);
- }
- }
-
// TODO: Make this private
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
@UsedForTesting
@@ -3090,7 +2271,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
if (repeatCount > 0) {
- if (code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) {
+ if (code == Constants.CODE_DELETE && !mInputLogic.mConnection.canDeleteCharacters()) {
// No need to feedback when repeat delete key will have no effect.
return;
}
@@ -3144,9 +2325,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// onHardwareKeyEvent, like onKeyDown returns true if it handled the event, false if
// it doesn't know what to do with it and leave it to the application. For example,
// hardware key events for adjusting the screen's brightness are passed as is.
- if (mEventInterpreter.onHardwareKeyEvent(event)) {
+ if (mInputLogic.mEventInterpreter.onHardwareKeyEvent(event)) {
final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
- mCurrentlyPressedHardwareKeys.add(keyIdentifier);
+ mInputLogic.mCurrentlyPressedHardwareKeys.add(keyIdentifier);
return true;
}
return super.onKeyDown(keyCode, event);
@@ -3155,7 +2336,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public boolean onKeyUp(final int keyCode, final KeyEvent event) {
final long keyIdentifier = event.getDeviceId() << 32 + event.getKeyCode();
- if (mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
+ if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
return true;
}
return super.onKeyUp(keyCode, event);
@@ -3187,7 +2368,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void launchKeyboardedDialogActivity(final Class<? extends Activity> activityClass) {
// Put the text in the attached EditText into a safe, saved state before switching to a
// new activity that will also use the soft keyboard.
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ mInputLogic.commitTyped(LastComposedWord.NOT_A_SEPARATOR);
launchSubActivity(activityClass);
}
@@ -3253,26 +2434,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// TODO: can this be removed somehow without breaking the tests?
@UsedForTesting
- /* package for test */ String getFirstSuggestedWord() {
- return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null;
+ /* package for test */ SuggestedWords getSuggestedWords() {
+ // You may not use this method for anything else than debug
+ return DEBUG ? mInputLogic.mSuggestedWords : 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();
+ return mInputLogic.mSuggest.isCurrentlyWaitingForMainDictionary();
}
// DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
@UsedForTesting
/* package for test */ void replaceMainDictionaryForTest(final Locale locale) {
- mSuggest.resetMainDict(this, locale, null);
+ mInputLogic.mSuggest.resetMainDict(this, locale, null);
}
public void debugDumpStateAndCrashWithException(final String context) {
@@ -3297,7 +2473,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
p.println(" mIsSuggestionsRequested = "
+ settingsValues.isSuggestionsRequested(mDisplayOrientation));
p.println(" mCorrectionEnabled=" + settingsValues.mCorrectionEnabled);
- p.println(" isComposingWord=" + mWordComposer.isComposingWord());
+ p.println(" isComposingWord=" + mInputLogic.mWordComposer.isComposingWord());
p.println(" mSoundOn=" + settingsValues.mSoundOn);
p.println(" mVibrateOn=" + settingsValues.mVibrateOn);
p.println(" mKeyPreviewPopupOn=" + settingsValues.mKeyPreviewPopupOn);
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
new file mode 100644
index 000000000..2f9221071
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -0,0 +1,1190 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.compat.SuggestionSpanUtils;
+import com.android.inputmethod.event.EventInterpreter;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.LastComposedWord;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.RichInputConnection;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
+import com.android.inputmethod.latin.utils.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.research.ResearchLogger;
+
+import java.util.TreeSet;
+
+/**
+ * This class manages the input logic.
+ */
+public final class InputLogic {
+ private static final String TAG = InputLogic.class.getSimpleName();
+
+ // TODO : Remove this member when we can.
+ private final LatinIME mLatinIME;
+
+ // TODO : make all these fields private as soon as possible.
+ // Current space state of the input method. This can be any of the above constants.
+ public int mSpaceState;
+ // Never null
+ public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ public Suggest mSuggest;
+ // The event interpreter should never be null.
+ public EventInterpreter mEventInterpreter;
+
+ public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ public final WordComposer mWordComposer;
+ public final RichInputConnection mConnection;
+ public final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
+
+ // Keep track of the last selection range to decide if we need to show word alternatives
+ public int mLastSelectionStart = Constants.NOT_A_CURSOR_POSITION;
+ public int mLastSelectionEnd = Constants.NOT_A_CURSOR_POSITION;
+
+ public int mDeleteCount;
+ private long mLastKeyTime;
+ public final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
+
+ // Keeps track of most recently inserted text (multi-character key) for reverting
+ public String mEnteredText;
+
+ // TODO: This boolean is persistent state and causes large side effects at unexpected times.
+ // Find a way to remove it for readability.
+ public boolean mIsAutoCorrectionIndicatorOn;
+
+ public InputLogic(final LatinIME latinIME) {
+ mLatinIME = latinIME;
+ mWordComposer = new WordComposer();
+ mEventInterpreter = new EventInterpreter(latinIME);
+ mConnection = new RichInputConnection(latinIME);
+ }
+
+ /**
+ * Initializes the input logic for input in an editor.
+ *
+ * Call this when input starts or restarts in some editor (typically, in onStartInputView).
+ * If the input is starting in the same field as before, set `restarting' to true. This allows
+ * the input logic to reset only necessary stuff and save performance. Also, when restarting
+ * some things must not be done (for example, the keyboard should not be reset to the
+ * alphabetic layout), so do not send false to this just in case.
+ *
+ * @param restarting whether input is starting in the same field as before.
+ */
+ public void startInput(final boolean restarting) {
+ }
+
+ /**
+ * Clean up the input logic after input is finished.
+ */
+ public void finishInput() {
+ }
+
+ /**
+ * React to a code input. It may be a code point to insert, or a symbolic value that influences
+ * the keyboard behavior.
+ *
+ * Typically, this is called whenever a key is pressed on the software keyboard. This is not
+ * the entry point for gesture input; see the onBatchInput* family of functions for this.
+ *
+ * @param code the code to handle. It may be a code point, or an internal key code.
+ * @param x the x-coordinate where the user pressed the key, or NOT_A_COORDINATE.
+ * @param y the y-coordinate where the user pressed the key, or NOT_A_COORDINATE.
+ */
+ public void onCodeInput(final int code, final int x, final int y,
+ // TODO: remove these three arguments
+ final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher,
+ final SubtypeSwitcher subtypeSwitcher) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_onCodeInput(code, x, y);
+ }
+ final SettingsValues settingsValues = Settings.getInstance().getCurrent();
+ final long when = SystemClock.uptimeMillis();
+ if (code != Constants.CODE_DELETE
+ || when > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
+ mDeleteCount = 0;
+ }
+ mLastKeyTime = when;
+ mConnection.beginBatchEdit();
+ final KeyboardSwitcher switcher = keyboardSwitcher;
+ // The space state depends only on the last character pressed and its own previous
+ // state. Here, we revert the space state to neutral if the key is actually modifying
+ // the input contents (any non-shift key), which is what we should do for
+ // all inputs that do not result in a special state. Each character handling is then
+ // free to override the state as they see fit.
+ final int spaceState = mSpaceState;
+ if (!mWordComposer.isComposingWord()) {
+ mIsAutoCorrectionIndicatorOn = false;
+ }
+
+ // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.
+ if (code != Constants.CODE_SPACE) {
+ handler.cancelDoubleSpacePeriodTimer();
+ }
+
+ boolean didAutoCorrect = false;
+ switch (code) {
+ case Constants.CODE_DELETE:
+ handleBackspace(settingsValues, spaceState, handler, keyboardSwitcher);
+ LatinImeLogger.logOnDelete(x, y);
+ break;
+ case Constants.CODE_SHIFT:
+ // Note: Calling back to the keyboard on Shift key is handled in
+ // {@link #onPressKey(int,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
+ // alphabetic shift and shift while in symbol layout.
+ performRecapitalization(settingsValues, keyboardSwitcher);
+ }
+ 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
+ // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+ break;
+ case Constants.CODE_SETTINGS:
+ onSettingsKeyPressed();
+ break;
+ case Constants.CODE_SHORTCUT:
+ subtypeSwitcher.switchToShortcutIME(mLatinIME);
+ break;
+ case Constants.CODE_ACTION_NEXT:
+ performEditorAction(EditorInfo.IME_ACTION_NEXT);
+ break;
+ case Constants.CODE_ACTION_PREVIOUS:
+ performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
+ break;
+ case Constants.CODE_LANGUAGE_SWITCH:
+ handleLanguageSwitchKey();
+ break;
+ case Constants.CODE_EMOJI:
+ // Note: Switching emoji keyboard is being handled in
+ // {@link KeyboardState#onCodeInput(int,int)}.
+ break;
+ case Constants.CODE_ENTER:
+ final EditorInfo editorInfo = getCurrentInputEditorInfo();
+ final int imeOptionsActionId =
+ InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
+ if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
+ // Either we have an actionLabel and we should performEditorAction with actionId
+ // regardless of its value.
+ performEditorAction(editorInfo.actionId);
+ } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
+ // We didn't have an actionLabel, but we had another action to execute.
+ // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
+ // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
+ // means there should be an action and the app didn't bother to set a specific
+ // code for it - presumably it only handles one. It does not have to be treated
+ // in any specific way: anything that is not IME_ACTION_NONE should be sent to
+ // performEditorAction.
+ performEditorAction(imeOptionsActionId);
+ } else {
+ // No action label, and the action from imeOptions is NONE: this is a regular
+ // enter key that should input a carriage return.
+ didAutoCorrect = handleNonSpecialCharacter(settingsValues,
+ Constants.CODE_ENTER, x, y, spaceState, keyboardSwitcher, handler);
+ }
+ break;
+ case Constants.CODE_SHIFT_ENTER:
+ didAutoCorrect = handleNonSpecialCharacter(settingsValues,
+ Constants.CODE_ENTER, x, y, spaceState, keyboardSwitcher, handler);
+ break;
+ default:
+ didAutoCorrect = handleNonSpecialCharacter(settingsValues,
+ code, x, y, spaceState, keyboardSwitcher, handler);
+ break;
+ }
+ switcher.onCodeInput(code);
+ // Reset after any single keystroke, except shift, capslock, and symbol-shift
+ if (!didAutoCorrect && code != Constants.CODE_SHIFT
+ && code != Constants.CODE_CAPSLOCK
+ && code != Constants.CODE_SWITCH_ALPHA_SYMBOL)
+ mLastComposedWord.deactivate();
+ if (Constants.CODE_DELETE != code) {
+ mEnteredText = null;
+ }
+ mConnection.endBatchEdit();
+ }
+
+ /**
+ * Handle inputting a code point to the editor.
+ *
+ * Non-special keys are those that generate a single code point.
+ * This includes all letters, digits, punctuation, separators, emoji. It excludes keys that
+ * manage keyboard-related stuff like shift, language switch, settings, layout switch, or
+ * any key that results in multiple code points like the ".com" key.
+ *
+ * @param settingsValues The current settings values.
+ * @param codePoint the code point associated with the key.
+ * @param x the x-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
+ * @param y the y-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
+ * @param spaceState the space state at start of the batch input.
+ * @return whether this caused an auto-correction to happen.
+ */
+ private boolean handleNonSpecialCharacter(final SettingsValues settingsValues,
+ final int codePoint, final int x, final int y, final int spaceState,
+ // TODO: remove these arguments
+ final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+ mSpaceState = SpaceState.NONE;
+ final boolean didAutoCorrect;
+ if (settingsValues.isWordSeparator(codePoint)
+ || Character.getType(codePoint) == Character.OTHER_SYMBOL) {
+ didAutoCorrect = handleSeparator(settingsValues, codePoint, x, y, spaceState,
+ keyboardSwitcher, handler);
+ } else {
+ didAutoCorrect = false;
+ if (SpaceState.PHANTOM == spaceState) {
+ if (settingsValues.mIsInternal) {
+ if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
+ LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ",
+ mWordComposer);
+ }
+ }
+ 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 character at the current cursor position.
+ resetEntireInputState(settingsValues, mLastSelectionStart, mLastSelectionEnd);
+ } else {
+ commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ }
+ }
+ final int keyX, keyY;
+ final Keyboard keyboard = keyboardSwitcher.getKeyboard();
+ if (keyboard != null && keyboard.hasProximityCharsCorrection(codePoint)) {
+ keyX = x;
+ keyY = y;
+ } else {
+ keyX = Constants.NOT_A_COORDINATE;
+ keyY = Constants.NOT_A_COORDINATE;
+ }
+ handleNonSeparator(settingsValues, codePoint, keyX, keyY, spaceState,
+ keyboardSwitcher, handler);
+ }
+ return didAutoCorrect;
+ }
+
+ /**
+ * Handle a non-separator.
+ * @param settingsValues The current settings values.
+ * @param codePoint the code point associated with the key.
+ * @param x the x-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
+ * @param y the y-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
+ * @param spaceState the space state at start of the batch input.
+ */
+ private void handleNonSeparator(final SettingsValues settingsValues,
+ final int codePoint, final int x, final int y, final int spaceState,
+ // TODO: Remove these arguments
+ final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+ // TODO: refactor this method to stop flipping isComposingWord around all the time, and
+ // make it shorter (possibly cut into several pieces). Also factor handleNonSpecialCharacter
+ // which has the same name as other handle* methods but is not the same.
+ boolean isComposingWord = mWordComposer.isComposingWord();
+
+ // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
+ // See onStartBatchInput() to see how to do it.
+ if (SpaceState.PHANTOM == spaceState && !settingsValues.isWordConnector(codePoint)) {
+ if (isComposingWord) {
+ // Sanity check
+ throw new RuntimeException("Should not be composing here");
+ }
+ promotePhantomSpace(settingsValues);
+ }
+
+ 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 character at the current cursor position.
+ resetEntireInputState(settingsValues, mLastSelectionStart, mLastSelectionEnd);
+ isComposingWord = false;
+ }
+ // We want to find out whether to start composing a new word with this character. If so,
+ // we need to reset the composing state and switch isComposingWord. The order of the
+ // tests is important for good performance.
+ // We only start composing if we're not already composing.
+ if (!isComposingWord
+ // We only start composing if this is a word code point. Essentially that means it's a
+ // a letter or a word connector.
+ && settingsValues.isWordCodePoint(codePoint)
+ // We never go into composing state if suggestions are not requested.
+ && settingsValues.isSuggestionsRequested(mLatinIME.mDisplayOrientation) &&
+ // In languages with spaces, we only start composing a word when we are not already
+ // touching a word. In languages without spaces, the above conditions are sufficient.
+ (!mConnection.isCursorTouchingWord(settingsValues)
+ || !settingsValues.mCurrentLanguageHasSpaces)) {
+ // Reset entirely the composing state anyway, then start composing a new word unless
+ // the character is a single quote or a dash. The idea here is, single quote and dash
+ // are not separators and they should be treated as normal characters, except in the
+ // first position where they should not start composing a word.
+ isComposingWord = (Constants.CODE_SINGLE_QUOTE != codePoint
+ && Constants.CODE_DASH != codePoint);
+ // Here we don't need to reset the last composed word. It will be reset
+ // when we commit this one, if we ever do; if on the other hand we backspace
+ // it entirely and resume suggestions on the previous word, we'd like to still
+ // have touch coordinates for it.
+ resetComposingState(false /* alsoResetLastComposedWord */);
+ }
+ if (isComposingWord) {
+ final MainKeyboardView mainKeyboardView = keyboardSwitcher.getMainKeyboardView();
+ // TODO: We should reconsider which coordinate system should be used to represent
+ // keyboard event.
+ final int keyX = mainKeyboardView.getKeyX(x);
+ final int keyY = mainKeyboardView.getKeyY(y);
+ mWordComposer.add(codePoint, keyX, keyY);
+ // If it's the first letter, make note of auto-caps state
+ if (mWordComposer.size() == 1) {
+ // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
+ // yet, so the word we want is the 1st word before the cursor.
+ mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
+ getActualCapsMode(keyboardSwitcher),
+ getNthPreviousWordForSuggestion(settingsValues, 1 /* nthPreviousWord */));
+ }
+ mConnection.setComposingText(getTextWithUnderline(
+ mWordComposer.getTypedWord()), 1);
+ } else {
+ final boolean swapWeakSpace = maybeStripSpace(settingsValues,
+ codePoint, spaceState, Constants.SUGGESTION_STRIP_COORDINATE == x);
+
+ sendKeyCodePoint(codePoint);
+
+ if (swapWeakSpace) {
+ swapSwapperAndSpace(keyboardSwitcher);
+ mSpaceState = SpaceState.WEAK;
+ }
+ // In case the "add to dictionary" hint was still displayed.
+ mLatinIME.dismissAddToDictionaryHint();
+ }
+ handler.postUpdateSuggestionStrip();
+ if (settingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onNonSeparator((char)codePoint, x, y);
+ }
+ }
+
+ /**
+ * Handle input of a separator code point.
+ * @param settingsValues The current settings values.
+ * @param codePoint the code point associated with the key.
+ * @param x the x-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
+ * @param y the y-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable.
+ * @param spaceState the space state at start of the batch input.
+ * @return whether this caused an auto-correction to happen.
+ */
+ private boolean handleSeparator(final SettingsValues settingsValues,
+ final int codePoint, final int x, final int y, final int spaceState,
+ // TODO: remove these arguments
+ final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+ boolean didAutoCorrect = false;
+ // We avoid sending spaces in languages without spaces if we were composing.
+ final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
+ && !settingsValues.mCurrentLanguageHasSpaces
+ && mWordComposer.isComposingWord();
+ 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(settingsValues, mLastSelectionStart, mLastSelectionEnd);
+ }
+ // isComposingWord() may have changed since we stored wasComposing
+ if (mWordComposer.isComposingWord()) {
+ if (settingsValues.mCorrectionEnabled) {
+ final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
+ : StringUtils.newSingleCodePointString(codePoint);
+ commitCurrentAutoCorrection(settingsValues, separator, handler);
+ didAutoCorrect = true;
+ } else {
+ commitTyped(StringUtils.newSingleCodePointString(codePoint));
+ }
+ }
+
+ final boolean swapWeakSpace = maybeStripSpace(settingsValues, codePoint, spaceState,
+ Constants.SUGGESTION_STRIP_COORDINATE == x);
+
+ if (SpaceState.PHANTOM == spaceState &&
+ settingsValues.isUsuallyPrecededBySpace(codePoint)) {
+ promotePhantomSpace(settingsValues);
+ }
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleSeparator(codePoint, mWordComposer.isComposingWord());
+ }
+
+ if (!shouldAvoidSendingCode) {
+ sendKeyCodePoint(codePoint);
+ }
+
+ if (Constants.CODE_SPACE == codePoint) {
+ if (settingsValues.isSuggestionsRequested(mLatinIME.mDisplayOrientation)) {
+ if (maybeDoubleSpacePeriod(settingsValues, keyboardSwitcher, handler)) {
+ mSpaceState = SpaceState.DOUBLE;
+ } else if (!mLatinIME.isShowingPunctuationList()) {
+ mSpaceState = SpaceState.WEAK;
+ }
+ }
+
+ handler.startDoubleSpacePeriodTimer();
+ handler.postUpdateSuggestionStrip();
+ } else {
+ if (swapWeakSpace) {
+ swapSwapperAndSpace(keyboardSwitcher);
+ mSpaceState = SpaceState.SWAP_PUNCTUATION;
+ } else if (SpaceState.PHANTOM == spaceState
+ && settingsValues.isUsuallyFollowedBySpace(codePoint)) {
+ // 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
+ // then insert a comma and go on to typing the next word, I want the space to be
+ // inserted automatically before the next word, the same way it is when I don't
+ // input the comma.
+ // The case is a little different if the separator is a space stripper. Such a
+ // separator does not normally need a space on the right (that's the difference
+ // between swappers and strippers), so we should not stay in phantom space state if
+ // the separator is a stripper. Hence the additional test above.
+ mSpaceState = SpaceState.PHANTOM;
+ }
+
+ // Set punctuation right away. onUpdateSelection will fire but tests whether it is
+ // already displayed or not, so it's okay.
+ mLatinIME.setPunctuationSuggestions();
+ }
+ if (settingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onSeparator((char)codePoint, x, y);
+ }
+
+ keyboardSwitcher.updateShiftState();
+ return didAutoCorrect;
+ }
+
+ /**
+ * Handle a press on the backspace key.
+ * @param settingsValues The current settings values.
+ * @param spaceState The space state at start of this batch edit.
+ */
+ private void handleBackspace(final SettingsValues settingsValues, final int spaceState,
+ // TODO: remove these arguments
+ final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) {
+ mSpaceState = SpaceState.NONE;
+ mDeleteCount++;
+
+ // In many cases, we may have to put the keyboard in auto-shift state again. However
+ // we want to wait a few milliseconds before doing it to avoid the keyboard flashing
+ // during key repeat.
+ handler.postUpdateShiftState();
+
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can remove the character at the current cursor position.
+ resetEntireInputState(settingsValues, mLastSelectionStart, mLastSelectionEnd);
+ // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
+ }
+ if (mWordComposer.isComposingWord()) {
+ if (mWordComposer.isBatchMode()) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ final String word = mWordComposer.getTypedWord();
+ ResearchLogger.latinIME_handleBackspace_batch(word, 1);
+ }
+ final String rejectedSuggestion = mWordComposer.getTypedWord();
+ mWordComposer.reset();
+ mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
+ } else {
+ mWordComposer.deleteLast();
+ }
+ mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+ handler.postUpdateSuggestionStrip();
+ if (!mWordComposer.isComposingWord()) {
+ // If we just removed the last character, auto-caps mode may have changed so we
+ // need to re-evaluate.
+ keyboardSwitcher.updateShiftState();
+ }
+ } else {
+ if (mLastComposedWord.canRevertCommit()) {
+ if (settingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onAutoCorrectionCancellation();
+ }
+ revertCommit(settingsValues, keyboardSwitcher, handler);
+ return;
+ }
+ if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
+ // Cancel multi-character input: remove the text we just entered.
+ // This is triggered on backspace after a key that inputs multiple characters,
+ // like the smiley key or the .com key.
+ mConnection.deleteSurroundingText(mEnteredText.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
+ // reverting any autocorrect at this point. So we can safely return.
+ return;
+ }
+ if (SpaceState.DOUBLE == spaceState) {
+ handler.cancelDoubleSpacePeriodTimer();
+ if (mConnection.revertDoubleSpacePeriod()) {
+ // No need to reset mSpaceState, it has already be done (that's why we
+ // receive it as a parameter)
+ return;
+ }
+ } else if (SpaceState.SWAP_PUNCTUATION == spaceState) {
+ if (mConnection.revertSwapPunctuation()) {
+ // Likewise
+ return;
+ }
+ }
+
+ // No cancelling of commit/double space/swap: we have a regular backspace.
+ // We should backspace one char and restart suggestion if at the end of a word.
+ if (mLastSelectionStart != mLastSelectionEnd) {
+ // If there is a selection, remove it.
+ final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
+ mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
+ // Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to
+ // happen, and if it's wrong, the next call to onUpdateSelection will correct it,
+ // but we want to set it right away to avoid it being used with the wrong values
+ // later (typically, in a subsequent press on backspace).
+ mLastSelectionEnd = mLastSelectionStart;
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
+ false /* shouldUncommitLogUnit */);
+ }
+ } else {
+ // There is no selection, just delete one character.
+ if (Constants.NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
+ // This should never happen.
+ Log.e(TAG, "Backspace when we don't know the selection position");
+ }
+ if (mLatinIME.mAppWorkAroundsUtils.isBeforeJellyBean() ||
+ settingsValues.mInputAttributes.isTypeNull()) {
+ // There are two possible reasons to send a key event: either the field has
+ // type TYPE_NULL, in which case the keyboard should send events, or we are
+ // running in backward compatibility mode. Before Jelly bean, the keyboard
+ // would simulate a hardware keyboard event on pressing enter or delete. This
+ // is bad for many reasons (there are race conditions with commits) but some
+ // applications are relying on this behavior so we continue to support it for
+ // older apps, so we retain this behavior if the app has target SDK < JellyBean.
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ }
+ } else {
+ final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+ if (codePointBeforeCursor == Constants.NOT_A_CODE) {
+ // Nothing to delete before the cursor.
+ return;
+ }
+ final int lengthToDelete =
+ Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
+ mConnection.deleteSurroundingText(lengthToDelete, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(lengthToDelete,
+ true /* shouldUncommitLogUnit */);
+ }
+ if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
+ final int codePointBeforeCursorToDeleteAgain =
+ mConnection.getCodePointBeforeCursor();
+ if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
+ final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
+ codePointBeforeCursorToDeleteAgain) ? 2 : 1;
+ mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
+ true /* shouldUncommitLogUnit */);
+ }
+ }
+ }
+ }
+ }
+ // TODO: move mDisplayOrientation to CurrentSettings.
+ if (settingsValues.isSuggestionsRequested(mLatinIME.mDisplayOrientation)
+ && settingsValues.mCurrentLanguageHasSpaces) {
+ restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(settingsValues, keyboardSwitcher,
+ handler);
+ }
+ // We just removed a character. We need to update the auto-caps state.
+ keyboardSwitcher.updateShiftState();
+ }
+ }
+
+ /**
+ * Handle a press on the language switch key (the "globe key")
+ */
+ private void handleLanguageSwitchKey() {
+ mLatinIME.handleLanguageSwitchKey();
+ }
+
+ /**
+ * Swap a space with a space-swapping punctuation sign.
+ *
+ * This method will check that there are two characters before the cursor and that the first
+ * one is a space before it does the actual swapping.
+ */
+ // TODO: Remove this argument
+ private void swapSwapperAndSpace(final KeyboardSwitcher keyboardSwitcher) {
+ final CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
+ // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
+ if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == Constants.CODE_SPACE) {
+ mConnection.deleteSurroundingText(2, 0);
+ final String text = lastTwo.charAt(1) + " ";
+ mConnection.commitText(text, 1);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
+ }
+ keyboardSwitcher.updateShiftState();
+ }
+ }
+
+ /*
+ * Strip a trailing space if necessary and returns whether it's a swap weak space situation.
+ * @param settingsValues The current settings values.
+ * @param codePoint The code point that is about to be inserted.
+ * @param spaceState The space state at start of this batch edit.
+ * @param isFromSuggestionStrip Whether this code point is coming from the suggestion strip.
+ * @return whether we should swap the space instead of removing it.
+ */
+ private boolean maybeStripSpace(final SettingsValues settingsValues,
+ final int code, final int spaceState, final boolean isFromSuggestionStrip) {
+ if (Constants.CODE_ENTER == code && SpaceState.SWAP_PUNCTUATION == spaceState) {
+ mConnection.removeTrailingSpace();
+ return false;
+ }
+ if ((SpaceState.WEAK == spaceState || SpaceState.SWAP_PUNCTUATION == spaceState)
+ && isFromSuggestionStrip) {
+ if (settingsValues.isUsuallyPrecededBySpace(code)) return false;
+ if (settingsValues.isUsuallyFollowedBySpace(code)) return true;
+ mConnection.removeTrailingSpace();
+ }
+ return false;
+ }
+
+ /**
+ * Apply the double-space-to-period transformation if applicable.
+ *
+ * The double-space-to-period transformation means that we replace two spaces with a
+ * period-space sequence of characters. This typically happens when the user presses space
+ * twice in a row quickly.
+ * This method will check that the double-space-to-period is active in settings, that the
+ * two spaces have been input close enough together, and that the previous character allows
+ * for the transformation to take place. If all of these conditions are fulfilled, this
+ * method applies the transformation and returns true. Otherwise, it does nothing and
+ * returns false.
+ *
+ * @param settingsValues the current values of the settings.
+ * @return true if we applied the double-space-to-period transformation, false otherwise.
+ */
+ private boolean maybeDoubleSpacePeriod(final SettingsValues settingsValues,
+ // TODO: remove these arguments
+ final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+ if (!settingsValues.mUseDoubleSpacePeriod) return false;
+ if (!handler.isAcceptingDoubleSpacePeriod()) return false;
+ // We only do this when we see two spaces and an accepted code point before the cursor.
+ // The code point may be a surrogate pair but the two spaces may not, so we need 4 chars.
+ final CharSequence lastThree = mConnection.getTextBeforeCursor(4, 0);
+ if (null == lastThree) return false;
+ final int length = lastThree.length();
+ if (length < 3) return false;
+ if (lastThree.charAt(length - 1) != Constants.CODE_SPACE) return false;
+ if (lastThree.charAt(length - 2) != Constants.CODE_SPACE) return false;
+ // We know there are spaces in pos -1 and -2, and we have at least three chars.
+ // If we have only three chars, isSurrogatePairs can't return true as charAt(1) is a space,
+ // so this is fine.
+ final int firstCodePoint =
+ Character.isSurrogatePair(lastThree.charAt(0), lastThree.charAt(1)) ?
+ Character.codePointAt(lastThree, 0) : lastThree.charAt(length - 3);
+ if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
+ handler.cancelDoubleSpacePeriodTimer();
+ mConnection.deleteSurroundingText(2, 0);
+ final String textToInsert = new String(
+ new int[] { settingsValues.mSentenceSeparator, Constants.CODE_SPACE }, 0, 2);
+ mConnection.commitText(textToInsert, 1);
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
+ false /* isBatchMode */);
+ }
+ mWordComposer.discardPreviousWordForSuggestion();
+ keyboardSwitcher.updateShiftState();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether this code point can be followed by the double-space-to-period transformation.
+ *
+ * See #maybeDoubleSpaceToPeriod for details.
+ * Generally, most word characters can be followed by the double-space-to-period transformation,
+ * while most punctuation can't. Some punctuation however does allow for this to take place
+ * after them, like the closing parenthesis for example.
+ *
+ * @param codePoint the code point after which we may want to apply the transformation
+ * @return whether it's fine to apply the transformation after this code point.
+ */
+ private static boolean canBeFollowedByDoubleSpacePeriod(final int codePoint) {
+ // TODO: This should probably be a blacklist rather than a whitelist.
+ // TODO: This should probably be language-dependant...
+ return Character.isLetterOrDigit(codePoint)
+ || codePoint == Constants.CODE_SINGLE_QUOTE
+ || codePoint == Constants.CODE_DOUBLE_QUOTE
+ || codePoint == Constants.CODE_CLOSING_PARENTHESIS
+ || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET
+ || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET
+ || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET
+ || codePoint == Constants.CODE_PLUS
+ || codePoint == Constants.CODE_PERCENT
+ || Character.getType(codePoint) == Character.OTHER_SYMBOL;
+ }
+
+ /**
+ * Performs a recapitalization event.
+ * @param settingsValues The current settings values.
+ */
+ private void performRecapitalization(final SettingsValues settingsValues,
+ // TODO: remove this argument.
+ final KeyboardSwitcher keyboardSwitcher) {
+ if (mLastSelectionStart == mLastSelectionEnd) {
+ return; // No selection
+ }
+ // If we have a recapitalize in progress, use it; otherwise, create a new one.
+ if (!mRecapitalizeStatus.isActive()
+ || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ final CharSequence selectedText =
+ mConnection.getSelectedText(0 /* flags, 0 for no styles */);
+ if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection
+ mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd,
+ selectedText.toString(),
+ settingsValues.mLocale, settingsValues.mWordSeparators);
+ // We trim leading and trailing whitespace.
+ mRecapitalizeStatus.trim();
+ // Trimming the object may have changed the length of the string, and we need to
+ // reposition the selection handles accordingly. As this result in an IPC call,
+ // only do it if it's actually necessary, in other words if the recapitalize status
+ // is not set at the same place as before.
+ if (!mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
+ mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
+ }
+ }
+ mConnection.finishComposingText();
+ mRecapitalizeStatus.rotate();
+ final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
+ mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
+ mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
+ mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
+ mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
+ // Match the keyboard to the new state.
+ keyboardSwitcher.updateShiftState();
+ }
+
+ /**
+ * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
+ * word, otherwise do nothing.
+ * @param settingsValues the current values of the settings.
+ */
+ private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(
+ final SettingsValues settingsValues,
+ // TODO: remove these two arguments
+ final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+ final CharSequence word = mConnection.getWordBeforeCursorIfAtEndOfWord(settingsValues);
+ if (null != word) {
+ final String wordString = word.toString();
+ mWordComposer.setComposingWord(word,
+ // Previous word is the 2nd word before cursor because we are restarting on the
+ // 1st word before cursor.
+ getNthPreviousWordForSuggestion(settingsValues, 2 /* nthPreviousWord */),
+ keyboardSwitcher.getKeyboard());
+ final int length = word.length();
+ mConnection.deleteSurroundingText(length, 0);
+ mConnection.setComposingText(word, 1);
+ handler.postUpdateSuggestionStrip();
+ // TODO: Handle the case where the user manually moves the cursor and then backs up over
+ // a separator. In that case, the current log unit should not be uncommitted.
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString,
+ true /* dumpCurrentLogUnit */);
+ }
+ }
+ }
+
+ /**
+ * Reverts a previous commit with auto-correction.
+ *
+ * This is triggered upon pressing backspace just after a commit with auto-correction.
+ *
+ * @param settingsValues the current settings values.
+ */
+ private void revertCommit(final SettingsValues settingsValues,
+ // TODO: remove these arguments
+ final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+ final String previousWord = mLastComposedWord.mPrevWord;
+ final String originallyTypedWord = mLastComposedWord.mTypedWord;
+ final String committedWord = mLastComposedWord.mCommittedWord;
+ final int cancelLength = committedWord.length();
+ // We want java chars, not codepoints for the following.
+ final int separatorLength = mLastComposedWord.mSeparatorString.length();
+ // TODO: should we check our saved separator against the actual contents of the text view?
+ final int deleteLength = cancelLength + separatorLength;
+ if (LatinIME.DEBUG) {
+ if (mWordComposer.isComposingWord()) {
+ throw new RuntimeException("revertCommit, but we are composing a word");
+ }
+ final CharSequence wordBeforeCursor =
+ mConnection.getTextBeforeCursor(deleteLength, 0).subSequence(0, cancelLength);
+ if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
+ throw new RuntimeException("revertCommit check failed: we thought we were "
+ + "reverting \"" + committedWord
+ + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
+ }
+ }
+ mConnection.deleteSurroundingText(deleteLength, 0);
+ if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
+ if (mSuggest != null) {
+ mSuggest.cancelAddingUserHistory(previousWord, committedWord);
+ }
+ }
+ final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
+ if (settingsValues.mCurrentLanguageHasSpaces) {
+ // For languages with spaces, we revert to the typed string, but the cursor is still
+ // after the separator so we don't resume suggestions. If the user wants to correct
+ // the word, they have to press backspace again.
+ mConnection.commitText(stringToCommit, 1);
+ } else {
+ // For languages without spaces, we revert the typed string but the cursor is flush
+ // with the typed word, so we need to resume suggestions right away.
+ mWordComposer.setComposingWord(stringToCommit, previousWord,
+ keyboardSwitcher.getKeyboard());
+ mConnection.setComposingText(stringToCommit, 1);
+ }
+ if (settingsValues.mIsInternal) {
+ 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);
+ }
+ // Don't restart suggestion yet. We'll restart if the user deletes the
+ // separator.
+ mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ // We have a separator between the word and the cursor: we should show predictions.
+ handler.postUpdateSuggestionStrip();
+ }
+
+ /**
+ * Factor in auto-caps and manual caps and compute the current caps mode.
+ * @param keyboardSwitcher the keyboard switcher. Caps mode depends on its mode.
+ * @return the actual caps mode the keyboard is in right now.
+ */
+ // TODO: Make this private
+ public int getActualCapsMode(final KeyboardSwitcher keyboardSwitcher) {
+ final int keyboardShiftMode = keyboardSwitcher.getKeyboardShiftMode();
+ if (keyboardShiftMode != WordComposer.CAPS_MODE_AUTO_SHIFTED) return keyboardShiftMode;
+ final int auto = mLatinIME.getCurrentAutoCapsState();
+ if (0 != (auto & TextUtils.CAP_MODE_CHARACTERS)) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED;
+ }
+ if (0 != auto) {
+ return WordComposer.CAPS_MODE_AUTO_SHIFTED;
+ }
+ return WordComposer.CAPS_MODE_OFF;
+ }
+
+ /**
+ * @return the editor info for the current editor
+ */
+ private EditorInfo getCurrentInputEditorInfo() {
+ return mLatinIME.getCurrentInputEditorInfo();
+ }
+
+ /**
+ * Get the nth previous word before the cursor as context for the suggestion process.
+ * @param currentSettings the current settings values.
+ * @param nthPreviousWord reverse index of the word to get (1-indexed)
+ * @return the nth previous word before the cursor.
+ */
+ // TODO: Make this private
+ public String getNthPreviousWordForSuggestion(final SettingsValues currentSettings,
+ final int nthPreviousWord) {
+ if (currentSettings.mCurrentLanguageHasSpaces) {
+ // If we are typing in a language with spaces we can just look up the previous
+ // word from textview.
+ return mConnection.getNthPreviousWord(currentSettings, nthPreviousWord);
+ } else {
+ return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
+ : mLastComposedWord.mCommittedWord;
+ }
+ }
+
+ /**
+ * @param actionId the action to perform
+ */
+ private void performEditorAction(final int actionId) {
+ mConnection.performEditorAction(actionId);
+ }
+
+ /**
+ * Handle a press on the settings key.
+ */
+ private void onSettingsKeyPressed() {
+ mLatinIME.onSettingsKeyPressed();
+ }
+
+ /**
+ * Resets the whole input state to the starting state.
+ *
+ * This will clear the composing word, reset the last composed word, clear the suggestion
+ * strip and tell the input connection about it so that it can refresh its caches.
+ *
+ * @param settingsValues the current values of the settings.
+ * @param newSelStart the new selection start, in java characters.
+ * @param newSelEnd the new selection end, in java characters.
+ */
+ // TODO: how is this different from startInput ?!
+ // TODO: remove all references to this in LatinIME and make this private
+ public void resetEntireInputState(final SettingsValues settingsValues,
+ final int newSelStart, final int newSelEnd) {
+ final boolean shouldFinishComposition = mWordComposer.isComposingWord();
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ if (settingsValues.mBigramPredictionEnabled) {
+ mLatinIME.clearSuggestionStrip();
+ } else {
+ mLatinIME.setSuggestedWords(settingsValues.mSuggestPuncList, false);
+ }
+ mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd,
+ shouldFinishComposition);
+ }
+
+ /**
+ * Resets only the composing state.
+ *
+ * Compare #resetEntireInputState, which also clears the suggestion strip and resets the
+ * input connection caches. This only deals with the composing state.
+ *
+ * @param alsoResetLastComposedWord whether to also reset the last composed word.
+ */
+ // TODO: remove all references to this in LatinIME and make this private.
+ public void resetComposingState(final boolean alsoResetLastComposedWord) {
+ mWordComposer.reset();
+ if (alsoResetLastComposedWord) {
+ mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ }
+ }
+
+ /**
+ * Gets a chunk of text with or the auto-correction indicator underline span as appropriate.
+ *
+ * This method looks at the old state of the auto-correction indicator to put or not put
+ * the underline span as appropriate. It is important to note that this does not correspond
+ * exactly to whether this word will be auto-corrected to or not: what's important here is
+ * to keep the same indication as before.
+ * When we add a new code point to a composing word, we don't know yet if we are going to
+ * auto-correct it until the suggestions are computed. But in the mean time, we still need
+ * to display the character and to extend the previous underline. To avoid any flickering,
+ * the underline should keep the same color it used to have, even if that's not ultimately
+ * the correct color for this new word. When the suggestions are finished evaluating, we
+ * will call this method again to fix the color of the underline.
+ *
+ * @param text the text on which to maybe apply the span.
+ * @return the same text, with the auto-correction underline span if that's appropriate.
+ */
+ // TODO: remove all references to this in LatinIME and make this private. Also, shouldn't
+ // this go in some *Utils class instead?
+ public CharSequence getTextWithUnderline(final String text) {
+ return mIsAutoCorrectionIndicatorOn
+ ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(mLatinIME, text)
+ : text;
+ }
+
+ /**
+ * Sends a DOWN key event followed by an UP key event to the editor.
+ *
+ * If possible at all, avoid using this method. It causes all sorts of race conditions with
+ * the text view because it goes through a different, asynchronous binder. Also, batch edits
+ * are ignored for key events. Use the normal software input methods instead.
+ *
+ * @param keyCode the key code to send inside the key event.
+ */
+ private void sendDownUpKeyEvent(final int keyCode) {
+ final long eventTime = SystemClock.uptimeMillis();
+ mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
+ }
+
+ /**
+ * Sends a code point to the editor, using the most appropriate method.
+ *
+ * Normally we send code points with commitText, but there are some cases (where backward
+ * compatibility is a concern for example) where we want to use deprecated methods.
+ *
+ * @param codePoint the code point to send.
+ */
+ private void sendKeyCodePoint(final int codePoint) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_sendKeyCodePoint(codePoint);
+ }
+ // TODO: Remove this special handling of digit letters.
+ // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
+ if (codePoint >= '0' && codePoint <= '9') {
+ sendDownUpKeyEvent(codePoint - '0' + KeyEvent.KEYCODE_0);
+ return;
+ }
+
+ // TODO: we should do this also when the editor has TYPE_NULL
+ if (Constants.CODE_ENTER == codePoint
+ && mLatinIME.mAppWorkAroundsUtils.isBeforeJellyBean()) {
+ // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
+ // a hardware keyboard event on pressing enter or delete. This is bad for many
+ // reasons (there are race conditions with commits) but some applications are
+ // relying on this behavior so we continue to support it for older apps.
+ sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER);
+ } else {
+ mConnection.commitText(StringUtils.newSingleCodePointString(codePoint), 1);
+ }
+ }
+
+ /**
+ * Promote a phantom space to an actual space.
+ *
+ * This essentially inserts a space, and that's it. It just checks the options and the text
+ * before the cursor are appropriate before doing it.
+ *
+ * @param settingsValues the current values of the settings.
+ */
+ // TODO: Make this private.
+ public void promotePhantomSpace(final SettingsValues settingsValues) {
+ if (settingsValues.shouldInsertSpacesAutomatically()
+ && settingsValues.mCurrentLanguageHasSpaces
+ && !mConnection.textBeforeCursorLooksLikeURL()) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.latinIME_promotePhantomSpace();
+ }
+ sendKeyCodePoint(Constants.CODE_SPACE);
+ }
+ }
+
+ /**
+ * Commit the typed string to the editor.
+ *
+ * This is typically called when we should commit the currently composing word without applying
+ * auto-correction to it. Typically, we come here upon pressing a separator when the keyboard
+ * is configured to not do auto-correction at all (because of the settings or the properties of
+ * the editor). In this case, `separatorString' is set to the separator that was pressed.
+ * We also come here in a variety of cases with external user action. For example, when the
+ * cursor is moved while there is a composition, or when the keyboard is closed, or when the
+ * user presses the Send button for an SMS, we don't auto-correct as that would be unexpected.
+ * In this case, `separatorString' is set to NOT_A_SEPARATOR.
+ *
+ * @param separatorString the separator string that's causing the commit, or NOT_A_SEPARATOR if
+ * none.
+ */
+ // TODO: Make this private
+ public void commitTyped(final String separatorString) {
+ if (!mWordComposer.isComposingWord()) return;
+ final String typedWord = mWordComposer.getTypedWord();
+ if (typedWord.length() > 0) {
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
+ }
+ mLatinIME.commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
+ separatorString);
+ }
+ }
+
+ /**
+ * Commit the current auto-correction.
+ *
+ * This will commit the best guess of the keyboard regarding what the user meant by typing
+ * the currently composing word. The IME computes suggestions and assigns a confidence score
+ * to each of them; when it's confident enough in one suggestion, it replaces the typed string
+ * by this suggestion at commit time. When it's not confident enough, or when it has no
+ * suggestions, or when the settings or environment does not allow for auto-correction, then
+ * this method just commits the typed string.
+ * Note that if suggestions are currently being computed in the background, this method will
+ * block until the computation returns. This is necessary for consistency (it would be very
+ * strange if pressing space would commit a different word depending on how fast you press).
+ *
+ * @param settingsValues the current value of the settings.
+ * @param separator the separator that's causing the commit to happen.
+ */
+ // TODO: Make this private
+ public void commitCurrentAutoCorrection(final SettingsValues settingsValues,
+ final String separator,
+ // TODO: Remove this argument.
+ final LatinIME.UIHandler handler) {
+ // Complete any pending suggestions query first
+ if (handler.hasPendingUpdateSuggestions()) {
+ mLatinIME.updateSuggestionStrip();
+ }
+ final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
+ final String typedWord = mWordComposer.getTypedWord();
+ final String autoCorrection = (typedAutoCorrection != null)
+ ? typedAutoCorrection : typedWord;
+ if (autoCorrection != null) {
+ if (TextUtils.isEmpty(typedWord)) {
+ throw new RuntimeException("We have an auto-correction but the typed word "
+ + "is empty? Impossible! I must commit suicide.");
+ }
+ if (settingsValues.mIsInternal) {
+ LatinImeLoggerUtils.onAutoCorrection(
+ typedWord, autoCorrection, separator, mWordComposer);
+ }
+ if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+ final SuggestedWords suggestedWords = mSuggestedWords;
+ ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
+ separator, mWordComposer.isBatchMode(), suggestedWords);
+ }
+ mLatinIME.commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
+ separator);
+ if (!typedWord.equals(autoCorrection)) {
+ // This will make the correction flash for a short while as a visual clue
+ // to the user that auto-correction happened. It has no other effect; in particular
+ // note that this won't affect the text inside the text field AT ALL: it only makes
+ // the segment of text starting at the supplied index and running for the length
+ // of the auto-correction flash. At this moment, the "typedWord" argument is
+ // ignored by TextView.
+ mConnection.commitCorrection(
+ new CorrectionInfo(mLastSelectionEnd - typedWord.length(),
+ typedWord, autoCorrection));
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java b/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java
new file mode 100644
index 000000000..ce80c0016
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/SpaceState.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+/**
+ * Class for managing space states.
+ *
+ * At any given time, the input logic is in one of five possible space states. Depending on the
+ * current space state, some behavior will change; the prime example of this is the PHANTOM state,
+ * in which any subsequent letter input will input a space before the letter. Read on the
+ * description inside this class for each of the space states.
+ */
+public class SpaceState {
+ // None: the state where all the keyboard behavior is the most "standard" and no automatic
+ // input is added or removed. In this state, all self-inserting keys only insert themselves,
+ // and backspace removes one character.
+ public static final int NONE = 0;
+ // Double space: the state where the user pressed space twice quickly, which LatinIME
+ // resolved as period-space. In this state, pressing backspace will undo the
+ // double-space-to-period insertion: it will replace ". " with " ".
+ public static final int DOUBLE = 1;
+ // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip
+ // have just been swapped. In this state, pressing backspace will undo the swap: the
+ // characters will be swapped back back, and the space state will go to WEAK.
+ public static final int SWAP_PUNCTUATION = 2;
+ // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak
+ // spaces happen when the user presses space, accepting the current suggestion (whether
+ // it's an auto-correction or not). In this state, pressing a punctuation from the suggestion
+ // strip inserts it before the space (while it inserts it after the space in the NONE state).
+ public static final int WEAK = 3;
+ // Phantom space: a not-yet-inserted space that should get inserted on the next input,
+ // character provided it's not a separator. If it's a separator, the phantom space is dropped.
+ // Phantom spaces happen when a user chooses a word from the suggestion strip. In this state,
+ // non-separators insert a space before they get inserted.
+ public static final int PHANTOM = 4;
+
+ private SpaceState() {
+ // This class is not publicly instantiable.
+ }
+}