diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 71a71d6fd41b0a16573dc8acc38d99e90d5b0030..c02b37b82f7bc1a88d78e94d5b08506fbe412b9e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -20,7 +20,6 @@ import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
@@ -44,7 +43,8 @@ import java.lang.ref.SoftReference;
 import java.util.HashMap;
 import java.util.Locale;
 
-public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener {
+public class KeyboardSwitcher implements KeyboardState.SwitchActions,
+        SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
     private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG;
     public static final boolean DEBUG_STATE = false;
@@ -87,20 +87,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
      * what user actually typed. */
     private boolean mIsAutoCorrectionActive;
 
-    // TODO: Encapsulate these state handling to separate class and combine with ShiftKeyState
-    // and ModifierKeyState into KeyboardState.
-    private static final int SWITCH_STATE_ALPHA = 0;
-    private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
-    private static final int SWITCH_STATE_SYMBOL = 2;
-    // The following states are used only on the distinct multi-touch panel devices.
-    private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
-    private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
-    private static final int SWITCH_STATE_CHORDING_ALPHA = 5;
-    private static final int SWITCH_STATE_CHORDING_SYMBOL = 6;
-    private int mSwitchState = SWITCH_STATE_ALPHA;
-
-    private String mLayoutSwitchBackSymbols;
-
     private int mThemeIndex = -1;
     private Context mThemeContext;
 
@@ -123,10 +109,17 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
                 mIsShifted = isSymbolShifted();
             }
             mIsValid = true;
+            if (DEBUG_STATE) {
+                Log.d(TAG, "save: alphabet=" + mIsAlphabetMode + " shiftLocked=" + mIsShiftLocked
+                        + " shift=" + mIsShifted);
+            }
         }
 
         public void restore() {
-            mPrevMainKeyboardWasShiftLocked = false;
+            if (DEBUG_STATE) {
+                Log.d(TAG, "restore: valid=" + mIsValid + " alphabet=" + mIsAlphabetMode
+                        + " shiftLocked=" + mIsShiftLocked + " shift=" + mIsShifted);
+            }
             if (!mIsValid || mIsAlphabetMode) {
                 setAlphabetKeyboard();
             } else {
@@ -167,7 +160,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         mResources = ims.getResources();
         mPrefs = prefs;
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
-        mState = new KeyboardState();
+        mState = new KeyboardState(this);
         setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
         prefs.registerOnSharedPreferenceChangeListener(this);
     }
@@ -199,8 +192,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
             mMainKeyboardId = getKeyboardId(editorInfo, false, false, settingsValues);
             mSymbolsKeyboardId = getKeyboardId(editorInfo, true, false, settingsValues);
             mSymbolsShiftedKeyboardId = getKeyboardId(editorInfo, true, true, settingsValues);
-            mState.onLoadKeyboard();
-            mLayoutSwitchBackSymbols = mResources.getString(R.string.layout_switch_back_symbols);
+            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
+            mPrevMainKeyboardWasShiftLocked = false;
             mSavedKeyboardState.restore();
         } catch (RuntimeException e) {
             Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
@@ -227,7 +220,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         mKeyboardView.setKeyboard(keyboard);
         mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
         mCurrentId = keyboard.mId;
-        mSwitchState = getSwitchState();
+        mState.onSetKeyboard(isAlphabetMode());
         updateShiftLockState(keyboard);
         mKeyboardView.setKeyPreviewPopupEnabled(
                 Settings.Values.isKeyPreviewPopupEnabled(mPrefs, mResources),
@@ -238,10 +231,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         updateShiftState();
     }
 
-    private int getSwitchState() {
-        return isAlphabetMode() ? SWITCH_STATE_ALPHA : SWITCH_STATE_SYMBOL_BEGIN;
-    }
-
     private void updateShiftLockState(Keyboard keyboard) {
         if (mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
             // Symbol keyboard may have an ALT key that has a caps lock style indicator (a.k.a.
@@ -377,7 +366,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         return mState.isManualTemporaryUpperCase();
     }
 
-    private void setShifted(int shiftMode) {
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setShifted(int shiftMode) {
         mInputMethodService.mHandler.cancelUpdateShiftState();
         LatinKeyboard latinKeyboard = getLatinKeyboard();
         if (latinKeyboard == null)
@@ -401,7 +392,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         mKeyboardView.invalidateAllKeys();
     }
 
-    private void setShiftLocked(boolean shiftLocked) {
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setShiftLocked(boolean shiftLocked) {
         mInputMethodService.mHandler.cancelUpdateShiftState();
         LatinKeyboard latinKeyboard = getLatinKeyboard();
         if (latinKeyboard == null)
@@ -463,19 +456,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
             Log.d(TAG, "updateShiftState: " + mState
                     + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState());
         }
-        final boolean isAlphabetMode = isAlphabetMode();
-        final boolean isShiftLocked = mState.isShiftLocked();
-        if (isAlphabetMode) {
-            if (!isShiftLocked && !mState.isShiftKeyIgnoring()) {
-                if (mState.isShiftKeyReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
-                    // Only when shift key is releasing, automatic temporary upper case will be set.
-                    setShifted(AUTOMATIC_SHIFT);
-                } else {
-                    setShifted(mState.isShiftKeyMomentary() ? MANUAL_SHIFT : UNSHIFT);
-                }
-            }
-        }
-        mState.onUpdateShiftState(isAlphabetMode);
+        mState.onUpdateShiftState(isAlphabetMode(), mInputMethodService.getCurrentAutoCapsState());
     }
 
     public void onPressShift(boolean withSliding) {
@@ -484,33 +465,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         if (DEBUG_STATE) {
             Log.d(TAG, "onPressShift: " + mState + " sliding=" + withSliding);
         }
-        final boolean isAlphabetMode = isAlphabetMode();
-        final boolean isShiftLocked = mState.isShiftLocked();
-        final boolean isAutomaticTemporaryUpperCase = mState.isAutomaticTemporaryUpperCase();
-        final boolean isShiftedOrShiftLocked = mState.isShiftedOrShiftLocked();
-        if (isAlphabetMode) {
-            if (isShiftLocked) {
-                // Shift key is pressed while caps lock state, we will treat this state as shifted
-                // caps lock state and mark as if shift key pressed while normal state.
-                setShifted(MANUAL_SHIFT);
-            } else if (isAutomaticTemporaryUpperCase) {
-                // Shift key is pressed while automatic temporary upper case, we have to move to
-                // manual temporary upper case.
-                setShifted(MANUAL_SHIFT);
-            } else if (isShiftedOrShiftLocked) {
-                // In manual upper case state, we just record shift key has been pressing while
-                // shifted state.
-            } else {
-                // In base layout, chording or manual temporary upper case mode is started.
-                setShifted(MANUAL_SHIFT);
-            }
-        } else {
-            // In symbol mode, just toggle symbol and symbol more keyboard.
-            toggleShiftInSymbols();
-            mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
-        }
-        mState.onPressShift(isAlphabetMode, isShiftLocked, isAutomaticTemporaryUpperCase,
-                isShiftedOrShiftLocked);
+        mState.onPressShift(isAlphabetMode(), isSymbolShifted());
     }
 
     public void onReleaseShift(boolean withSliding) {
@@ -519,61 +474,21 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         if (DEBUG_STATE) {
             Log.d(TAG, "onReleaseShift: " + mState + " sliding=" + withSliding);
         }
-        final boolean isAlphabetMode = isAlphabetMode();
-        final boolean isShiftLocked = mState.isShiftLocked();
-        final boolean isShiftLockShifted = mState.isShiftLockShifted();
-        final boolean isShiftedOrShiftLocked = mState.isShiftedOrShiftLocked();
-        final boolean isManualTemporaryUpperCaseFromAuto =
-                mState.isManualTemporaryUpperCaseFromAuto();
-        if (isAlphabetMode) {
-            if (mState.isShiftKeyMomentary()) {
-                // After chording input while normal state.
-                setShifted(UNSHIFT);
-            } else if (isShiftLocked && !isShiftLockShifted && (mState.isShiftKeyPressing()
-                    || mState.isShiftKeyPressingOnShifted()) && !withSliding) {
-                // Shift has been long pressed, ignore this release.
-            } else if (isShiftLocked && !mState.isShiftKeyIgnoring() && !withSliding) {
-                // Shift has been pressed without chording while caps lock state.
-                setShiftLocked(false);
-            } else if (isShiftedOrShiftLocked && mState.isShiftKeyPressingOnShifted()
-                    && !withSliding) {
-                // Shift has been pressed without chording while shifted state.
-                setShifted(UNSHIFT);
-            } else if (isManualTemporaryUpperCaseFromAuto && mState.isShiftKeyPressing()
-                    && !withSliding) {
-                // Shift has been pressed without chording while manual temporary upper case
-                // transited from automatic temporary upper case.
-                setShifted(UNSHIFT);
-            }
-        } else {
-            // In symbol mode, snap back to the previous keyboard mode if the user chords the shift
-            // key and another key, then releases the shift key.
-            if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) {
-                toggleShiftInSymbols();
-            }
-        }
-        mState.onReleaseShift();
+        mState.onReleaseShift(isAlphabetMode(), isSymbolShifted(), withSliding);
     }
 
     public void onPressSymbol() {
         if (DEBUG_STATE) {
             Log.d(TAG, "onPressSymbol: " + mState);
         }
-        toggleAlphabetAndSymbols();
-        mState.onPressSymbol();
-        mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
+        mState.onPressSymbol(isAlphabetMode());
     }
 
     public void onReleaseSymbol() {
         if (DEBUG_STATE) {
             Log.d(TAG, "onReleaseSymbol: " + mState);
-            }
-        // Snap back to the previous keyboard mode if the user chords the mode change key and
-        // another key, then releases the mode change key.
-        if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) {
-            toggleAlphabetAndSymbols();
         }
-        mState.onReleaseSymbol();
+        mState.onReleaseSymbol(isAlphabetMode());
     }
 
     public void onOtherKeyPressed() {
@@ -584,30 +499,28 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
     }
 
     public void onCancelInput() {
-        // Snap back to the previous keyboard mode if the user cancels sliding input.
-        if (isSinglePointer()) {
-            if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) {
-                toggleAlphabetAndSymbols();
-            } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) {
-                toggleShiftInSymbols();
-            }
-        }
+        mState.onCancelInput(isAlphabetMode(), isSymbolShifted(), isSinglePointer());
     }
 
     // TODO: Move this variable to KeyboardState.
     private boolean mPrevMainKeyboardWasShiftLocked;
 
-    private void setSymbolsKeyboard() {
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setSymbolsKeyboard() {
         mPrevMainKeyboardWasShiftLocked = mState.isShiftLocked();
         setKeyboard(getKeyboard(mSymbolsKeyboardId));
     }
 
-    private void setAlphabetKeyboard() {
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setAlphabetKeyboard() {
         setKeyboard(getKeyboard(mMainKeyboardId));
         setShiftLocked(mPrevMainKeyboardWasShiftLocked);
         mPrevMainKeyboardWasShiftLocked = false;
     }
 
+    // TODO: Remove this method and merge into toggleKeyboardMode().
     private void toggleAlphabetAndSymbols() {
         if (isAlphabetMode()) {
             setSymbolsKeyboard();
@@ -620,10 +533,13 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         return mCurrentId != null && mCurrentId.equals(mSymbolsShiftedKeyboardId);
     }
 
-    private void setSymbolsShiftedKeyboard() {
+    // Implements {@link KeyboardState.SwitchActions}.
+    @Override
+    public void setSymbolsShiftedKeyboard() {
         setKeyboard(getKeyboard(mSymbolsShiftedKeyboardId));
     }
 
+    // TODO: Remove this method and merge into toggleShift().
     private void toggleShiftInSymbols() {
         if (isSymbolShifted()) {
             setSymbolsKeyboard();
@@ -633,8 +549,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
     }
 
     public boolean isInMomentarySwitchState() {
-        return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
-                || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
+        return mState.isInMomentarySwitchState();
     }
 
     public boolean isVibrateAndSoundFeedbackRequired() {
@@ -649,84 +564,15 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
     }
 
-    private static boolean isSpaceCharacter(int c) {
-        return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER;
-    }
-
-    private boolean isLayoutSwitchBackCharacter(int c) {
-        if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false;
-        if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true;
-        return false;
-    }
-
     /**
      * Updates state machine to figure out when to automatically snap back to the previous mode.
      */
-    public void onKey(int code) {
+    public void onCodeInput(int code) {
         if (DEBUG_STATE) {
-            Log.d(TAG, "onKey: code=" + code + " switchState=" + mSwitchState
-                    + " isSinglePointer=" + isSinglePointer());
-        }
-        switch (mSwitchState) {
-        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
-            // Only distinct multi touch devices can be in this state.
-            // On non-distinct multi touch devices, mode change key is handled by
-            // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and
-            // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts
-            // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from
-            // {@link #SWITCH_STATE_MOMENTARY}.
-            if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
-                // Detected only the mode change key has been pressed, and then released.
-                if (mCurrentId.equals(mMainKeyboardId)) {
-                    mSwitchState = SWITCH_STATE_ALPHA;
-                } else {
-                    mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
-                }
-            } else if (isSinglePointer()) {
-                // Snap back to the previous keyboard mode if the user pressed the mode change key
-                // and slid to other key, then released the finger.
-                // If the user cancels the sliding input, snapping back to the previous keyboard
-                // mode is handled by {@link #onCancelInput}.
-                toggleAlphabetAndSymbols();
-            } else {
-                // Chording input is being started. The keyboard mode will be snapped back to the
-                // previous mode in {@link onReleaseSymbol} when the mode change key is released.
-                mSwitchState = SWITCH_STATE_CHORDING_ALPHA;
-            }
-            break;
-        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
-            if (code == Keyboard.CODE_SHIFT) {
-                // Detected only the shift key has been pressed on symbol layout, and then released.
-                mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
-            } else if (isSinglePointer()) {
-                // Snap back to the previous keyboard mode if the user pressed the shift key on
-                // symbol mode and slid to other key, then released the finger.
-                toggleShiftInSymbols();
-                mSwitchState = SWITCH_STATE_SYMBOL;
-            } else {
-                // Chording input is being started. The keyboard mode will be snapped back to the
-                // previous mode in {@link onReleaseShift} when the shift key is released.
-                mSwitchState = SWITCH_STATE_CHORDING_SYMBOL;
-            }
-            break;
-        case SWITCH_STATE_SYMBOL_BEGIN:
-            if (!isSpaceCharacter(code) && code >= 0) {
-                mSwitchState = SWITCH_STATE_SYMBOL;
-            }
-            // Snap back to alpha keyboard mode immediately if user types a quote character.
-            if (isLayoutSwitchBackCharacter(code)) {
-                setAlphabetKeyboard();
-            }
-            break;
-        case SWITCH_STATE_SYMBOL:
-        case SWITCH_STATE_CHORDING_SYMBOL:
-            // Snap back to alpha keyboard mode if user types one or more non-space/enter
-            // characters followed by a space/enter or a quote character.
-            if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) {
-                setAlphabetKeyboard();
-            }
-            break;
+            Log.d(TAG, "onCodeInput: code=" + code + " isSinglePointer=" + isSinglePointer()
+                    + " " + mState);
         }
+        mState.onCodeInput(isAlphabetMode(), isSymbolShifted(), code, isSinglePointer());
     }
 
     public LatinKeyboardView getKeyboardView() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index e5615940557c20998eb5eedffa250e97b6d8288f..a632a27b16dca8000f06d64f06b64566acc0cad3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -16,24 +16,59 @@
 
 package com.android.inputmethod.keyboard.internal;
 
+import android.text.TextUtils;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
 // TODO: Add unit tests
 public class KeyboardState {
+    public interface SwitchActions {
+        public void setAlphabetKeyboard();
+        public static final int UNSHIFT = 0;
+        public static final int MANUAL_SHIFT = 1;
+        public static final int AUTOMATIC_SHIFT = 2;
+        public void setShifted(int shiftMode);
+        public void setShiftLocked(boolean shiftLocked);
+        public void setSymbolsKeyboard();
+        public void setSymbolsShiftedKeyboard();
+    }
+
     private KeyboardShiftState mKeyboardShiftState = new KeyboardShiftState();
 
-    // TODO: Combine these key state objects with auto mode switch state.
     private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
     private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
 
-    public KeyboardState() {
+    private static final int SWITCH_STATE_ALPHA = 0;
+    private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
+    private static final int SWITCH_STATE_SYMBOL = 2;
+    // The following states are used only on the distinct multi-touch panel devices.
+    private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
+    private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
+    private static final int SWITCH_STATE_CHORDING_ALPHA = 5;
+    private static final int SWITCH_STATE_CHORDING_SYMBOL = 6;
+    private int mSwitchState = SWITCH_STATE_ALPHA;
+
+    private String mLayoutSwitchBackSymbols;
+
+    private final SwitchActions mSwitchActions;
+
+    public KeyboardState(SwitchActions switchActions) {
+        mSwitchActions = switchActions;
     }
 
-    public void onLoadKeyboard() {
+    public void onLoadKeyboard(String layoutSwitchBackSymbols) {
+        mLayoutSwitchBackSymbols = layoutSwitchBackSymbols;
         mKeyboardShiftState.setShifted(false);
         mKeyboardShiftState.setShiftLocked(false);
         mShiftKeyState.onRelease();
         mSymbolKeyState.onRelease();
     }
 
+    // TODO: Get rid of this method
+    public void onSetKeyboard(boolean isAlphabetMode) {
+        mSwitchState = isAlphabetMode ? SWITCH_STATE_ALPHA : SWITCH_STATE_SYMBOL_BEGIN;
+    }
+
     public boolean isShiftLocked() {
         return mKeyboardShiftState.isShiftLocked();
     }
@@ -73,40 +108,40 @@ public class KeyboardState {
         mKeyboardShiftState.setAutomaticTemporaryUpperCase();
     }
 
-    // TODO: Get rid of this method
-    public boolean isShiftKeyIgnoring() {
-        return mShiftKeyState.isIgnoring();
-    }
-
-    // TODO: Get rid of this method
-    public boolean isShiftKeyReleasing() {
-        return mShiftKeyState.isReleasing();
-    }
-
-    // TODO: Get rid of this method
-    public boolean isShiftKeyMomentary() {
-        return mShiftKeyState.isMomentary();
-    }
-
-    // TODO: Get rid of this method
-    public boolean isShiftKeyPressing() {
-        return mShiftKeyState.isPressing();
+    private void toggleAlphabetAndSymbols(boolean isAlphabetMode) {
+        if (isAlphabetMode) {
+            mSwitchActions.setSymbolsKeyboard();
+        } else {
+            mSwitchActions.setAlphabetKeyboard();
+        }
     }
 
-    // TODO: Get rid of this method
-    public boolean isShiftKeyPressingOnShifted() {
-        return mShiftKeyState.isPressingOnShifted();
+    private void toggleShiftInSymbols(boolean isSymbolShifted) {
+        if (isSymbolShifted) {
+            mSwitchActions.setSymbolsKeyboard();
+        } else {
+            mSwitchActions.setSymbolsShiftedKeyboard();
+        }
     }
 
     public void onReleaseCapsLock() {
         mShiftKeyState.onRelease();
     }
 
-    public void onPressSymbol() {
+    // TODO: Get rid of isAlphabetMode argument.
+    public void onPressSymbol(boolean isAlphabetMode) {
+        toggleAlphabetAndSymbols(isAlphabetMode);
         mSymbolKeyState.onPress();
+        mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
     }
 
-    public void onReleaseSymbol() {
+    // TODO: Get rid of isAlphabetMode argument.
+    public void onReleaseSymbol(boolean isAlphabetMode) {
+        // Snap back to the previous keyboard mode if the user chords the mode change key and
+        // another key, then releases the mode change key.
+        if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) {
+            toggleAlphabetAndSymbols(isAlphabetMode);
+        }
         mSymbolKeyState.onRelease();
     }
 
@@ -115,48 +150,200 @@ public class KeyboardState {
         mSymbolKeyState.onOtherKeyPressed();
     }
 
-    public void onUpdateShiftState(boolean isAlphabetMode) {
-        if (!isAlphabetMode) {
+    // TODO: Get rid of isAlphabetMode argument.
+    public void onUpdateShiftState(boolean isAlphabetMode, boolean autoCaps) {
+        if (isAlphabetMode) {
+            if (!isShiftLocked() && !mShiftKeyState.isIgnoring()) {
+                if (mShiftKeyState.isReleasing() && autoCaps) {
+                    // Only when shift key is releasing, automatic temporary upper case will be set.
+                    mSwitchActions.setShifted(SwitchActions.AUTOMATIC_SHIFT);
+                } else {
+                    mSwitchActions.setShifted(mShiftKeyState.isMomentary()
+                            ? SwitchActions.MANUAL_SHIFT : SwitchActions.UNSHIFT);
+                }
+            }
+        } else {
             // In symbol keyboard mode, we should clear shift key state because only alphabet
             // keyboard has shift key.
             mSymbolKeyState.onRelease();
         }
     }
 
-    // TODO: Get rid of these boolean arguments.
-    public void onPressShift(boolean isAlphabetMode, boolean isShiftLocked,
-            boolean isAutomaticTemporaryUpperCase, boolean isShiftedOrShiftLocked) {
+    // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments.
+    public void onPressShift(boolean isAlphabetMode, boolean isSymbolShifted) {
         if (isAlphabetMode) {
-            if (isShiftLocked) {
+            if (isShiftLocked()) {
                 // Shift key is pressed while caps lock state, we will treat this state as shifted
                 // caps lock state and mark as if shift key pressed while normal state.
+                mSwitchActions.setShifted(SwitchActions.MANUAL_SHIFT);
                 mShiftKeyState.onPress();
-            } else if (isAutomaticTemporaryUpperCase) {
+            } else if (isAutomaticTemporaryUpperCase()) {
                 // Shift key is pressed while automatic temporary upper case, we have to move to
                 // manual temporary upper case.
+                mSwitchActions.setShifted(SwitchActions.MANUAL_SHIFT);
                 mShiftKeyState.onPress();
-            } else if (isShiftedOrShiftLocked) {
+            } else if (isShiftedOrShiftLocked()) {
                 // In manual upper case state, we just record shift key has been pressing while
                 // shifted state.
                 mShiftKeyState.onPressOnShifted();
             } else {
                 // In base layout, chording or manual temporary upper case mode is started.
+                mSwitchActions.setShifted(SwitchActions.MANUAL_SHIFT);
                 mShiftKeyState.onPress();
             }
         } else {
             // In symbol mode, just toggle symbol and symbol more keyboard.
+            toggleShiftInSymbols(isSymbolShifted);
+            mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
             mShiftKeyState.onPress();
         }
     }
 
-    public void onReleaseShift() {
+    // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments.
+    public void onReleaseShift(boolean isAlphabetMode, boolean isSymbolShifted,
+            boolean withSliding) {
+        if (isAlphabetMode) {
+            final boolean isShiftLocked = isShiftLocked();
+            if (mShiftKeyState.isMomentary()) {
+                // After chording input while normal state.
+                mSwitchActions.setShifted(SwitchActions.UNSHIFT);
+            } else if (isShiftLocked && !isShiftLockShifted() && (mShiftKeyState.isPressing()
+                    || mShiftKeyState.isPressingOnShifted()) && !withSliding) {
+                // Shift has been long pressed, ignore this release.
+            } else if (isShiftLocked && !mShiftKeyState.isIgnoring() && !withSliding) {
+                // Shift has been pressed without chording while caps lock state.
+                mSwitchActions.setShiftLocked(false);
+            } else if (isShiftedOrShiftLocked() && mShiftKeyState.isPressingOnShifted()
+                    && !withSliding) {
+                // Shift has been pressed without chording while shifted state.
+                mSwitchActions.setShifted(SwitchActions.UNSHIFT);
+            } else if (isManualTemporaryUpperCaseFromAuto() && mShiftKeyState.isPressing()
+                    && !withSliding) {
+                // Shift has been pressed without chording while manual temporary upper case
+                // transited from automatic temporary upper case.
+                mSwitchActions.setShifted(SwitchActions.UNSHIFT);
+            }
+        } else {
+            // In symbol mode, snap back to the previous keyboard mode if the user chords the shift
+            // key and another key, then releases the shift key.
+            if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) {
+                toggleShiftInSymbols(isSymbolShifted);
+            }
+        }
         mShiftKeyState.onRelease();
     }
 
+    // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments.
+    public void onCancelInput(boolean isAlphabetMode, boolean isSymbolShifted,
+            boolean isSinglePointer) {
+        // Snap back to the previous keyboard mode if the user cancels sliding input.
+        if (isSinglePointer) {
+            if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) {
+                toggleAlphabetAndSymbols(isAlphabetMode);
+            } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) {
+                toggleShiftInSymbols(isSymbolShifted);
+            }
+        }
+    }
+
+    public boolean isInMomentarySwitchState() {
+        return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
+                || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
+    }
+
+    private static boolean isSpaceCharacter(int c) {
+        return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER;
+    }
+
+    private boolean isLayoutSwitchBackCharacter(int c) {
+        if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false;
+        if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true;
+        return false;
+    }
+
+    // TODO: Get rid of isAlphabetMode and isSymbolShifted arguments.
+    public void onCodeInput(boolean isAlphabetMode, boolean isSymbolShifted, int code,
+            boolean isSinglePointer) {
+        switch (mSwitchState) {
+        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
+            // Only distinct multi touch devices can be in this state.
+            // On non-distinct multi touch devices, mode change key is handled by
+            // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and
+            // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts
+            // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from
+            // {@link #SWITCH_STATE_MOMENTARY}.
+            if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+                // Detected only the mode change key has been pressed, and then released.
+                if (isAlphabetMode) {
+                    mSwitchState = SWITCH_STATE_ALPHA;
+                } else {
+                    mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+                }
+            } else if (isSinglePointer) {
+                // Snap back to the previous keyboard mode if the user pressed the mode change key
+                // and slid to other key, then released the finger.
+                // If the user cancels the sliding input, snapping back to the previous keyboard
+                // mode is handled by {@link #onCancelInput}.
+                toggleAlphabetAndSymbols(isAlphabetMode);
+            } else {
+                // Chording input is being started. The keyboard mode will be snapped back to the
+                // previous mode in {@link onReleaseSymbol} when the mode change key is released.
+                mSwitchState = SWITCH_STATE_CHORDING_ALPHA;
+            }
+            break;
+        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
+            if (code == Keyboard.CODE_SHIFT) {
+                // Detected only the shift key has been pressed on symbol layout, and then released.
+                mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
+            } else if (isSinglePointer) {
+                // Snap back to the previous keyboard mode if the user pressed the shift key on
+                // symbol mode and slid to other key, then released the finger.
+                toggleShiftInSymbols(isSymbolShifted);
+                mSwitchState = SWITCH_STATE_SYMBOL;
+            } else {
+                // Chording input is being started. The keyboard mode will be snapped back to the
+                // previous mode in {@link onReleaseShift} when the shift key is released.
+                mSwitchState = SWITCH_STATE_CHORDING_SYMBOL;
+            }
+            break;
+        case SWITCH_STATE_SYMBOL_BEGIN:
+            if (!isSpaceCharacter(code) && code >= 0) {
+                mSwitchState = SWITCH_STATE_SYMBOL;
+            }
+            // Snap back to alpha keyboard mode immediately if user types a quote character.
+            if (isLayoutSwitchBackCharacter(code)) {
+                mSwitchActions.setAlphabetKeyboard();
+            }
+            break;
+        case SWITCH_STATE_SYMBOL:
+        case SWITCH_STATE_CHORDING_SYMBOL:
+            // Snap back to alpha keyboard mode if user types one or more non-space/enter
+            // characters followed by a space/enter or a quote character.
+            if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) {
+                mSwitchActions.setAlphabetKeyboard();
+            }
+            break;
+        }
+    }
+
+    private static String switchStateToString(int switchState) {
+        switch (switchState) {
+        case SWITCH_STATE_ALPHA: return "ALPHA";
+        case SWITCH_STATE_SYMBOL_BEGIN: return "SYMBOL-BEGIN";
+        case SWITCH_STATE_SYMBOL: return "SYMBOL";
+        case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL";
+        case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE";
+        case SWITCH_STATE_CHORDING_ALPHA: return "CHORDING-ALPHA";
+        case SWITCH_STATE_CHORDING_SYMBOL: return "CHORDING-SYMBOL";
+        default: return null;
+        }
+    }
+
     @Override
     public String toString() {
         return "[keyboard=" + mKeyboardShiftState
                 + " shift=" + mShiftKeyState
-                + " symbol=" + mSymbolKeyState + "]";
+                + " symbol=" + mSymbolKeyState
+                + " switch=" + switchStateToString(mSwitchState) + "]";
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index c2656b8915e07d6187ea9cecb9f0d42e0c5e093f..ab41b101893e19d5abb366d48f0eb44faf481c93 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1374,7 +1374,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             mExpectingUpdateSelection = true;
             break;
         }
-        switcher.onKey(primaryCode);
+        switcher.onCodeInput(primaryCode);
         // Reset after any single keystroke
         mEnteredText = null;
     }
@@ -1390,7 +1390,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         ic.commitText(text, 1);
         ic.endBatchEdit();
         mKeyboardSwitcher.updateShiftState();
-        mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
+        mKeyboardSwitcher.onCodeInput(Keyboard.CODE_DUMMY);
         mSpaceState = SPACE_STATE_NONE;
         mEnteredText = text;
     }