diff --git a/java/res/drawable-hdpi/sym_keyboard_language_switch.png b/java/res/drawable-hdpi/sym_keyboard_language_switch.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa747642d9d91642bf56f769150e095fa372b542
Binary files /dev/null and b/java/res/drawable-hdpi/sym_keyboard_language_switch.png differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_language_switch.png b/java/res/drawable-mdpi/sym_keyboard_language_switch.png
new file mode 100644
index 0000000000000000000000000000000000000000..f30c1b640ab60b07865cec8340143a7b1d9303ff
Binary files /dev/null and b/java/res/drawable-mdpi/sym_keyboard_language_switch.png differ
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index a5d590e04de3f2a4b3f6750de2068ed7ce717724..7758074208be18d03cb34ac249f92920db44eaeb 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -216,6 +216,7 @@
         <attr name="iconShiftKeyShifted" format="reference" />
         <attr name="iconDisabledShortcutKey" format="reference" />
         <attr name="iconPreviewTabKey" format="reference" />
+        <attr name="iconLanguageSwitchKey" format="reference" />
     </declare-styleable>
 
     <declare-styleable name="Keyboard_Key">
@@ -296,6 +297,7 @@
             <enum name="iconShortcutForLabel" value="9" />
             <enum name="iconSpaceKeyForNumberLayout" value="10" />
             <enum name="iconShiftKeyShifted" value="11" />
+            <enum name="iconLanguageSwitchKey" value="14" />
         </attr>
         <!-- The icon for disabled key -->
         <attr name="keyIconDisabled" format="enum">
@@ -361,6 +363,7 @@
         <attr name="clobberSettingsKey" format="boolean" />
         <attr name="shortcutKeyEnabled" format="boolean" />
         <attr name="hasShortcutKey" format="boolean" />
+        <attr name="languageSwitchKeyEnabled" format="boolean" />
         <attr name="isMultiLine" format="boolean" />
         <attr name="imeAction" format="enum">
             <!-- This should be aligned with EditorInfo.IME_ACTION_* -->
diff --git a/java/res/values/keyboard-icons-black.xml b/java/res/values/keyboard-icons-black.xml
index 1c5a5f720a922d75a5e8d5f7fe61afcc0bf199c8..44fc2b9c12cb35ee80ee8270c324741ff2bc3a79 100644
--- a/java/res/values/keyboard-icons-black.xml
+++ b/java/res/values/keyboard-icons-black.xml
@@ -34,5 +34,7 @@
         <item name="iconShiftKeyShifted">@drawable/sym_bkeyboard_shift_locked</item>
         <item name="iconDisabledShortcutKey">@drawable/sym_bkeyboard_voice_off</item>
         <item name="iconPreviewTabKey">@drawable/sym_keyboard_feedback_tab</item>
+        <!-- TODO: Needs dedicated black theme globe icon -->
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
     </style>
 </resources>
diff --git a/java/res/values/keyboard-icons-ics.xml b/java/res/values/keyboard-icons-ics.xml
index f68be5f1e8498004a19919b04d1e8c5cc3f06889..5fba0253db23b610bfc49d75069cefd1d23e99c2 100644
--- a/java/res/values/keyboard-icons-ics.xml
+++ b/java/res/values/keyboard-icons-ics.xml
@@ -33,5 +33,6 @@
         <item name="iconShiftKeyShifted">@drawable/sym_keyboard_shift_locked_holo</item>
         <item name="iconDisabledShortcutKey">@drawable/sym_keyboard_voice_off_holo</item>
         <item name="iconPreviewTabKey">@drawable/sym_keyboard_feedback_tab</item>
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
     </style>
 </resources>
diff --git a/java/res/values/keyboard-icons-white.xml b/java/res/values/keyboard-icons-white.xml
index 35197a1c01f2820cddf6247688bd4e10c442f813..837b1a37acb080df281a15f7cdcd5d8cbd5dd09f 100644
--- a/java/res/values/keyboard-icons-white.xml
+++ b/java/res/values/keyboard-icons-white.xml
@@ -31,5 +31,6 @@
         <!-- TODO: Needs non-holo disabled shortcut icon drawable -->
         <item name="iconDisabledShortcutKey">@drawable/sym_keyboard_voice_off_holo</item>
         <item name="iconPreviewTabKey">@drawable/sym_keyboard_feedback_tab</item>
+        <item name="iconLanguageSwitchKey">@drawable/sym_keyboard_language_switch</item>
     </style>
 </resources>
diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml
index 7f9e4bda48f852902c12ed6a77edc912c630dccb..d3d9b63244778346669e9278ccb8075e88cff804 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -32,5 +32,6 @@
     <integer name="key_action_enter">-7</integer>
     <integer name="key_action_next">-8</integer>
     <integer name="key_action_previous">-9</integer>
-    <integer name="key_unspecified">-10</integer>
+    <integer name="key_language_switch">-10</integer>
+    <integer name="key_unspecified">-11</integer>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index d8a3a689ecfdc6f2fb2a1a5d6a066cc0d15cebdc..f2c02d014edc903e5744e2cb3b4f9a386a4c5f05 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -64,6 +64,13 @@
     <!-- Option summary for advanced settings screen [CHAR LIMIT=65 (two lines) or 30 (fits on one line, preferable)] -->
     <string name="advanced_settings_summary">Options for experts</string>
 
+    <!-- Option name for including other IMEs in the language switch list [CHAR LIMIT=25] -->
+    <string name="include_other_imes_in_language_switch_list">Switch to other input methods</string>
+    <!-- Option summary for including other IMEs in the language switch list [CHAR LIMIT=65] -->
+    <string name="include_other_imes_in_language_switch_list_summary">Language switch key covers other input methods too</string>
+    <!-- Option to suppress language switch key [CHAR LIMIT=25] -->
+    <string name="suppress_language_switch_key">Suppress language switch key</string>
+
     <!-- Option for the dismiss delay of the key popup [CHAR LIMIT=25] -->
     <string name="key_preview_popup_dismiss_delay">Key popup dismiss delay</string>
     <!-- Description for delay for dismissing a popup on keypress: no delay [CHAR LIMIT=15] -->
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 76eacb673bdee594d42c65d683ca2faedae6c5ac..f153a7d96b6183a04e4bcdc8bac5f6122db89b40 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -117,10 +117,10 @@
         latin:altCode="@integer/key_space"
         latin:parentStyle="f1MoreKeysStyle" />
     <key-style
-        latin:styleName="settingsKeyStyle"
-        latin:code="@integer/key_settings"
-        latin:keyIcon="iconSettingsKey"
-        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping"
+        latin:styleName="languageSwitchKeyStyle"
+        latin:code="@integer/key_language_switch"
+        latin:keyIcon="iconLanguageSwitchKey"
+        latin:keyActionFlags="noKeyPreview|altCodeWhileTyping|enableLongPress"
         latin:altCode="@integer/key_space"
         latin:backgroundType="functional" />
     <key-style
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index f826ef006ea6c6424b1b6c4466bc7df45889b11b..ebca25089280c9af3dedbd8b009b94ef38096d60 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -92,6 +92,17 @@
             android:key="pref_advanced_settings"
             android:title="@string/advanced_settings"
             android:summary="@string/advanced_settings_summary">
+            <CheckBoxPreference
+                android:key="pref_suppress_language_switch_key"
+                android:title="@string/suppress_language_switch_key"
+                android:persistent="true"
+                android:defaultValue="false" />
+            <CheckBoxPreference
+                android:key="pref_include_other_imes_in_language_switch_list"
+                android:title="@string/include_other_imes_in_language_switch_list"
+                android:summary="@string/include_other_imes_in_language_switch_list_summary"
+                android:persistent="true"
+                android:defaultValue="false" />
             <!-- Values for popup dismiss delay are added programatically -->
             <ListPreference
                 android:key="pref_key_preview_popup_dismiss_delay"
diff --git a/java/res/xml/row_qwerty4.xml b/java/res/xml/row_qwerty4.xml
index df9c8fe3f04e1aff96ba5aa134146e83b4be8127..0dd2177e82d2927e3d1d1660d9c4ac569c8cdff0 100644
--- a/java/res/xml/row_qwerty4.xml
+++ b/java/res/xml/row_qwerty4.xml
@@ -57,9 +57,23 @@
                     latin:keyStyle="f1MoreKeysStyle" />
             </default>
         </switch>
-        <Key
-            latin:keyStyle="spaceKeyStyle"
-            latin:keyWidth="50%p" />
+        <switch>
+            <case
+                latin:languageSwitchKeyEnabled="true"
+            >
+                <Key
+                    latin:keyStyle="languageSwitchKeyStyle" />
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="40%p" />
+            </case>
+            <!-- languageSwitchKeyEnabled="false" -->
+            <default>
+                <Key
+                    latin:keyStyle="spaceKeyStyle"
+                    latin:keyWidth="50%p" />
+            </default>
+        </switch>
         <Key
             latin:keyStyle="punctuationKeyStyle" />
         <Key
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 7e216e5c8d61112d465ff7aeb1f9a47fa23cda75..9dd0a599de551afe4a6ccaa37efa84ed94595d16 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -49,6 +49,8 @@ public class InputMethodManagerCompatWrapper {
     private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName();
     private static final Method METHOD_getCurrentInputMethodSubtype =
             CompatUtils.getMethod(InputMethodManager.class, "getCurrentInputMethodSubtype");
+    private static final Method METHOD_getLastInputMethodSubtype =
+            CompatUtils.getMethod(InputMethodManager.class, "getLastInputMethodSubtype");
     private static final Method METHOD_getEnabledInputMethodSubtypeList =
             CompatUtils.getMethod(InputMethodManager.class, "getEnabledInputMethodSubtypeList",
                     InputMethodInfo.class, boolean.class);
@@ -60,6 +62,8 @@ public class InputMethodManagerCompatWrapper {
                     String.class, InputMethodSubtypeCompatWrapper.CLASS_InputMethodSubtype);
     private static final Method METHOD_switchToLastInputMethod = CompatUtils.getMethod(
             InputMethodManager.class, "switchToLastInputMethod", IBinder.class);
+    private static final Method METHOD_switchToNextInputMethod = CompatUtils.getMethod(
+            InputMethodManager.class, "switchToNextInputMethod", IBinder.class, Boolean.TYPE);
 
     private static final InputMethodManagerCompatWrapper sInstance =
             new InputMethodManagerCompatWrapper();
@@ -111,6 +115,15 @@ public class InputMethodManagerCompatWrapper {
         return new InputMethodSubtypeCompatWrapper(o);
     }
 
+    public InputMethodSubtypeCompatWrapper getLastInputMethodSubtype() {
+        if (!SUBTYPE_SUPPORTED) {
+            return new InputMethodSubtypeCompatWrapper(
+                    0, 0, mLanguageSwitcherProxy.getInputLocale().toString(), KEYBOARD_MODE, "");
+        }
+        Object o = CompatUtils.invoke(mImm, null, METHOD_getLastInputMethodSubtype);
+        return new InputMethodSubtypeCompatWrapper(o);
+    }
+
     public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList(
             InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) {
         if (!SUBTYPE_SUPPORTED) {
@@ -221,6 +234,14 @@ public class InputMethodManagerCompatWrapper {
         return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToLastInputMethod, token);
     }
 
+    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+        if (SubtypeSwitcher.getInstance().isDummyVoiceMode()) {
+            return true;
+        }
+        return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToNextInputMethod, token,
+                onlyCurrentIme);
+    }
+
     public List<InputMethodInfoCompatWrapper> getEnabledInputMethodList() {
         if (mImm == null) return null;
         List<InputMethodInfoCompatWrapper> imis = new ArrayList<InputMethodInfoCompatWrapper>();
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index c6cdf7986f9d77e42df76f4be547c030011ad8b9..5660d194260e2a8f9d21195a5d5b1e24478b5f27 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -99,8 +99,9 @@ public class Keyboard {
     public static final int CODE_ACTION_ENTER = -7;
     public static final int CODE_ACTION_NEXT = -8;
     public static final int CODE_ACTION_PREVIOUS = -9;
+    public static final int CODE_LANGUAGE_SWITCH = -10;
     // Code value representing the code is not specified.
-    public static final int CODE_UNSPECIFIED = -10;
+    public static final int CODE_UNSPECIFIED = -11;
 
     public final KeyboardId mId;
     public final int mThemeId;
@@ -1076,6 +1077,9 @@ public class Keyboard {
                         R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
                 final boolean hasShortcutKeyMatched = matchBoolean(a,
                         R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
+                final boolean languageSwitchKeyEnabledMatched = matchBoolean(a,
+                        R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+                        id.mLanguageSwitchKeyEnabled);
                 final boolean isMultiLineMatched = matchBoolean(a,
                         R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
                 final boolean imeActionMatched = matchInteger(a,
@@ -1089,11 +1093,12 @@ public class Keyboard {
                 final boolean selected = keyboardSetElementMatched && modeMatched
                         && navigateNextMatched && navigatePreviousMatched && passwordInputMatched
                         && clobberSettingsKeyMatched && shortcutKeyEnabledMatched
-                        && hasShortcutKeyMatched && isMultiLineMatched && imeActionMatched
-                        && localeCodeMatched && languageCodeMatched && countryCodeMatched;
+                        && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched
+                        && isMultiLineMatched && imeActionMatched && localeCodeMatched
+                        && languageCodeMatched && countryCodeMatched;
 
                 if (DEBUG) {
-                    startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+                    startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
                             textAttr(a.getString(R.styleable.Keyboard_Case_keyboardSetElement),
                                     "keyboardSetElement"),
                             textAttr(a.getString(R.styleable.Keyboard_Case_mode), "mode"),
@@ -1111,6 +1116,8 @@ public class Keyboard {
                                     "shortcutKeyEnabled"),
                             booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey,
                                     "hasShortcutKey"),
+                            booleanAttr(a, R.styleable.Keyboard_Case_languageSwitchKeyEnabled,
+                                    "languageSwitchKeyEnabled"),
                             booleanAttr(a, R.styleable.Keyboard_Case_isMultiLine,
                                     "isMultiLine"),
                             textAttr(a.getString(R.styleable.Keyboard_Case_localeCode),
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index f5752962e0274b3791577151d9535f9d927a704d..6703b93017a1a8055c886a9364095c7dbae6edb1 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -62,13 +62,14 @@ public class KeyboardId {
     public final boolean mClobberSettingsKey;
     public final boolean mShortcutKeyEnabled;
     public final boolean mHasShortcutKey;
+    public final boolean mLanguageSwitchKeyEnabled;
     public final String mCustomActionLabel;
 
     private final int mHashCode;
 
     public KeyboardId(int elementId, Locale locale, int orientation, int width, int mode,
             EditorInfo editorInfo, boolean clobberSettingsKey, boolean shortcutKeyEnabled,
-            boolean hasShortcutKey) {
+            boolean hasShortcutKey, boolean languageSwitchKeyEnabled) {
         this.mLocale = locale;
         this.mOrientation = orientation;
         this.mWidth = width;
@@ -78,6 +79,7 @@ public class KeyboardId {
         this.mClobberSettingsKey = clobberSettingsKey;
         this.mShortcutKeyEnabled = shortcutKeyEnabled;
         this.mHasShortcutKey = hasShortcutKey;
+        this.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
         this.mCustomActionLabel = (editorInfo.actionLabel != null)
                 ? editorInfo.actionLabel.toString() : null;
 
@@ -94,6 +96,7 @@ public class KeyboardId {
                 id.mClobberSettingsKey,
                 id.mShortcutKeyEnabled,
                 id.mHasShortcutKey,
+                id.mLanguageSwitchKeyEnabled,
                 id.isMultiLine(),
                 id.imeAction(),
                 id.mCustomActionLabel,
@@ -114,6 +117,7 @@ public class KeyboardId {
                 && other.mClobberSettingsKey == this.mClobberSettingsKey
                 && other.mShortcutKeyEnabled == this.mShortcutKeyEnabled
                 && other.mHasShortcutKey == this.mHasShortcutKey
+                && other.mLanguageSwitchKeyEnabled == this.mLanguageSwitchKeyEnabled
                 && other.isMultiLine() == this.isMultiLine()
                 && other.imeAction() == this.imeAction()
                 && TextUtils.equals(other.mCustomActionLabel, this.mCustomActionLabel)
@@ -172,7 +176,7 @@ public class KeyboardId {
 
     @Override
     public String toString() {
-        return String.format("[%s %s %s%d %s %s %s%s%s%s%s%s%s]",
+        return String.format("[%s %s %s%d %s %s %s%s%s%s%s%s%s%s]",
                 elementIdToName(mElementId),
                 mLocale,
                 (mOrientation == 1 ? "port" : "land"), mWidth,
@@ -184,6 +188,7 @@ public class KeyboardId {
                 (passwordInput() ? " passwordInput" : ""),
                 (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""),
                 (mHasShortcutKey ? " hasShortcutKey" : ""),
+                (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
                 (isMultiLine() ? "isMultiLine" : "")
         );
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
index ee882edc02a4700d7f128f3dec76d12b23fb64e4..731aaf7c52f27c81135a7c33f7bfd8dbd544c665 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSet.java
@@ -101,6 +101,7 @@ public class KeyboardSet {
         boolean mVoiceKeyEnabled;
         boolean mVoiceKeyOnMain;
         boolean mNoSettingsKey;
+        boolean mLanguageSwitchKeyEnabled;
         Locale mLocale;
         int mOrientation;
         int mWidth;
@@ -196,7 +197,7 @@ public class KeyboardSet {
                 && (isSymbols != params.mVoiceKeyOnMain);
         return new KeyboardId(keyboardSetElementId, params.mLocale, params.mOrientation,
                 params.mWidth, params.mMode, params.mEditorInfo, params.mNoSettingsKey,
-                params.mVoiceKeyEnabled, hasShortcutKey);
+                params.mVoiceKeyEnabled, hasShortcutKey, params.mLanguageSwitchKeyEnabled);
     }
 
     public static class Builder {
@@ -239,7 +240,8 @@ public class KeyboardSet {
             return this;
         }
 
-        public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain) {
+        public Builder setOptions(boolean voiceKeyEnabled, boolean voiceKeyOnMain,
+                boolean languageSwitchKeyEnabled) {
             @SuppressWarnings("deprecation")
             final boolean deprecatedNoMicrophone = Utils.inPrivateImeOptions(
                     null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, mEditorInfo);
@@ -248,6 +250,7 @@ public class KeyboardSet {
                     || deprecatedNoMicrophone;
             mParams.mVoiceKeyEnabled = voiceKeyEnabled && !noMicrophone;
             mParams.mVoiceKeyOnMain = voiceKeyOnMain;
+            mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled;
             return this;
         }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index e1c6f26049915e9d89d0b423ff1ac08970fe2f47..ac8dd1b958e2e3a86000497ee4b9a0b4ab2fe24d 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -133,7 +133,8 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions,
                         LatinIME.SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION));
         builder.setOptions(
                 settingsValues.isVoiceKeyEnabled(editorInfo),
-                settingsValues.isVoiceKeyOnMain());
+                settingsValues.isVoiceKeyOnMain(),
+                settingsValues.isLanguageSwitchKeyEnabled(mThemeContext));
         mKeyboardSet = builder.build();
         try {
             mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 78c371ff0cd388b44fcac88f8a6dc34beb2d8261..afc4932e93bdd1cd578b189ceaf03251a85b9549 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -489,7 +489,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
             KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode);
             return true;
         }
-        if (primaryCode == Keyboard.CODE_SPACE) {
+        if (primaryCode == Keyboard.CODE_SPACE || primaryCode == Keyboard.CODE_LANGUAGE_SWITCH) {
             // Long pressing the space key invokes IME switcher dialog.
             if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
                 tracker.onLongPressed();
@@ -782,6 +782,11 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke
                     && Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
                 drawKeyPopupHint(key, canvas, paint, params);
             }
+        } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) {
+            super.onDrawKeyTopVisuals(key, canvas, paint, params);
+            if (Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
+                drawKeyPopupHint(key, canvas, paint, params);
+            }
         } else {
             super.onDrawKeyTopVisuals(key, canvas, paint, params);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 7c8fd1225071eb7e8baa5120e89a5c5521da296a..ca711ec7d92b4734d4114b7efe90620f081b7e09 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -31,7 +31,7 @@ public class KeyboardIconsSet {
 
     // The value should be aligned with the enum value of Key.keyIcon.
     public static final int ICON_UNDEFINED = 0;
-    private static final int NUM_ICONS = 13;
+    private static final int NUM_ICONS = 14;
 
     private final Drawable[] mIcons = new Drawable[NUM_ICONS + 1];
 
@@ -57,6 +57,7 @@ public class KeyboardIconsSet {
         addIconIdMap(11, "shiftKeyShifted", R.styleable.Keyboard_iconShiftKeyShifted);
         addIconIdMap(12, "disabledShortcurKey", R.styleable.Keyboard_iconDisabledShortcutKey);
         addIconIdMap(13, "previewTabKey", R.styleable.Keyboard_iconPreviewTabKey);
+        addIconIdMap(14, "languageSwitchKey", R.styleable.Keyboard_iconLanguageSwitchKey);
     }
 
     private static void addIconIdMap(int iconId, String name, int attrId) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2bcd947f1566610e0fe35712a13c80e6fcad912d..cb22b4935fd0345a2142cfa64713180454ba039b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -29,6 +29,7 @@ import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
 import android.os.Debug;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.SystemClock;
 import android.preference.PreferenceActivity;
@@ -55,6 +56,7 @@ import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.compat.InputConnectionCompatUtils;
 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
 import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
 import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.compat.SuggestionSpanUtils;
 import com.android.inputmethod.compat.VibratorCompatWrapper;
@@ -196,6 +198,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     private KeyboardSwitcher mKeyboardSwitcher;
     private SubtypeSwitcher mSubtypeSwitcher;
     private VoiceProxy mVoiceProxy;
+    private boolean mShouldSwitchToLastSubtype = true;
 
     private UserDictionary mUserDictionary;
     private UserBigramDictionary mUserBigramDictionary;
@@ -1263,6 +1266,25 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         }
     }
 
+    private void handleLanguageSwitchKey() {
+        final boolean includesOtherImes = !mSettingsValues.mIncludesOtherImesInLanguageSwitchList;
+        final IBinder token = getWindow().getWindow().getAttributes().token;
+        if (mShouldSwitchToLastSubtype) {
+            final InputMethodSubtypeCompatWrapper lastSubtype = mImm.getLastInputMethodSubtype();
+            final boolean lastSubtypeBelongsToThisIme = Utils.checkIfSubtypeBelongsToThisIme(
+                    this, lastSubtype);
+            if ((includesOtherImes || lastSubtypeBelongsToThisIme)
+                    && mImm.switchToLastInputMethod(token)) {
+                mShouldSwitchToLastSubtype = false;
+            } else {
+                mImm.switchToNextInputMethod(token, !includesOtherImes);
+                mShouldSwitchToLastSubtype = true;
+            }
+        } else {
+            mImm.switchToNextInputMethod(token, !includesOtherImes);
+        }
+    }
+
     private void sendKeyCodePoint(int code) {
         // TODO: Remove this special handling of digit letters.
         // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
@@ -1306,6 +1328,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
             handleBackspace(spaceState);
             mDeleteCount++;
             mExpectingUpdateSelection = true;
+            mShouldSwitchToLastSubtype = true;
             LatinImeLogger.logOnDelete();
             break;
         case Keyboard.CODE_SHIFT:
@@ -1327,6 +1350,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         case Keyboard.CODE_ACTION_PREVIOUS:
             EditorInfoCompatUtils.performEditorActionPrevious(getCurrentInputConnection());
             break;
+        case Keyboard.CODE_LANGUAGE_SWITCH:
+            handleLanguageSwitchKey();
+            break;
         default:
             mSpaceState = SPACE_STATE_NONE;
             if (mSettingsValues.isWordSeparator(primaryCode)) {
@@ -1335,6 +1361,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
                 handleCharacter(primaryCode, x, y, spaceState);
             }
             mExpectingUpdateSelection = true;
+            mShouldSwitchToLastSubtype = true;
             break;
         }
         switcher.onCodeInput(primaryCode);
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 3029057be5ff01c867b4820759b8766426fc4b1e..305cef22d130bb642f4bfafc1a345d99570fdc5e 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -43,7 +43,6 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
 import android.widget.TextView;
 
 import com.android.inputmethod.compat.CompatUtils;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
 import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
 import com.android.inputmethod.compat.VibratorCompatWrapper;
 import com.android.inputmethod.deprecated.VoiceProxy;
@@ -73,6 +72,10 @@ public class Settings extends InputMethodSettingsActivity
     public static final String PREF_MISC_SETTINGS = "misc_settings";
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
     public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
+    public static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
+            "pref_suppress_language_switch_key";
+    public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
+            "pref_include_other_imes_in_language_switch_list";
     public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
             "pref_key_preview_popup_dismiss_delay";
     public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
@@ -204,6 +207,11 @@ public class Settings extends InputMethodSettingsActivity
             }
         }
 
+        final CheckBoxPreference includeOtherImesInLanguageSwitchList =
+                (CheckBoxPreference)findPreference(PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST);
+        includeOtherImesInLanguageSwitchList.setEnabled(
+                !SettingsValues.isLanguageSwitchKeySupressed(prefs));
+
         mKeyPreviewPopupDismissDelay =
                 (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
         final String[] entries = new String[] {
@@ -316,6 +324,12 @@ public class Settings extends InputMethodSettingsActivity
             if (null != popupDismissDelay) {
                 popupDismissDelay.setEnabled(prefs.getBoolean(PREF_POPUP_ON, true));
             }
+        } else if (key.equals(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) {
+            final CheckBoxPreference includeOtherImesInLanguageSwicthList =
+                    (CheckBoxPreference)findPreference(
+                            PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST);
+            includeOtherImesInLanguageSwicthList.setEnabled(
+                    !SettingsValues.isLanguageSwitchKeySupressed(prefs));
         }
         ensureConsistencyOfAutoCorrectionSettings();
         mVoiceOn = !(prefs.getString(PREF_VOICE_MODE, mVoiceModeOff)
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 6d65a74c8fa5d82db9fa9284df987b4485a8ae11..69e45f61959458816e3be901b408c169a13082d0 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -23,11 +23,14 @@ import android.os.Build;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
 import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.compat.VibratorCompatWrapper;
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
 
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
 
 public class SettingsValues {
@@ -55,6 +58,8 @@ public class SettingsValues {
     public final String mShowSuggestionsSetting;
     @SuppressWarnings("unused") // TODO: Use this
     private final boolean mUsabilityStudyMode;
+    public final boolean mIncludesOtherImesInLanguageSwitchList;
+    public final boolean mIsLanguageSwitchKeySuppressed;
     @SuppressWarnings("unused") // TODO: Use this
     private final String mKeyPreviewPopupDismissDelayRawValue;
     public final boolean mUseContactsDict;
@@ -127,6 +132,9 @@ public class SettingsValues {
         mShowSuggestionsSetting = prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING,
                 res.getString(R.string.prefs_suggestion_visibility_default_value));
         mUsabilityStudyMode = getUsabilityStudyMode(prefs);
+        mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean(
+                Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
+        mIsLanguageSwitchKeySuppressed = isLanguageSwitchKeySupressed(prefs);
         mKeyPreviewPopupDismissDelayRawValue = prefs.getString(
                 Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
                 Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout)));
@@ -309,6 +317,22 @@ public class SettingsValues {
         return mVoiceKeyOnMain;
     }
 
+    public static boolean isLanguageSwitchKeySupressed(SharedPreferences sp) {
+        return sp.getBoolean(Settings.PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false);
+    }
+
+    public boolean isLanguageSwitchKeyEnabled(Context context) {
+        if (mIsLanguageSwitchKeySuppressed) {
+            return false;
+        }
+        if (mIncludesOtherImesInLanguageSwitchList) {
+            return Utils.hasMultipleEnabledIMEsOrSubtypes(/* include aux subtypes */false);
+        } else {
+            return Utils.hasMultipleEnabledSubtypesInThisIme(
+                    context, /* include aux subtypes */false);
+        }
+    }
+
     public boolean isFullscreenModeAllowed(Resources res) {
         return res.getBoolean(R.bool.config_use_fullscreen_mode);
     }
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 33d4b877ea7a5fcacbce5670acd8726073871929..a8679e07addd5a3c1410b389016ff3645d515378 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -52,6 +52,7 @@ import java.io.PrintWriter;
 import java.nio.channels.FileChannel;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
@@ -117,16 +118,51 @@ public class Utils {
         }
     }
 
+    // TODO: Move InputMethodSubtype related utility methods to its own utility class.
+    // TODO: Cache my InputMethodInfo and/or InputMethodSubtype list.
+    public static boolean checkIfSubtypeBelongsToThisIme(Context context,
+            InputMethodSubtypeCompatWrapper ims) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
+
+        final InputMethodInfoCompatWrapper myImi = Utils.getInputMethodInfo(
+                context.getPackageName());
+        final List<InputMethodSubtypeCompatWrapper> subtypes =
+                imm.getEnabledInputMethodSubtypeList(myImi, true);
+        for (final InputMethodSubtypeCompatWrapper subtype : subtypes) {
+            if (subtype.equals(ims)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public static boolean hasMultipleEnabledIMEsOrSubtypes(
             final boolean shouldIncludeAuxiliarySubtypes) {
         final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
         if (imm == null) return false;
+
         final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
+        return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
+    }
+
+    public static boolean hasMultipleEnabledSubtypesInThisIme(Context context,
+            final boolean shouldIncludeAuxiliarySubtypes) {
+        final InputMethodInfoCompatWrapper myImi = Utils.getInputMethodInfo(
+                context.getPackageName());
+        final List<InputMethodInfoCompatWrapper> imiList = Collections.singletonList(myImi);
+        return Utils.hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
+    }
+
+    private static boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
+            List<InputMethodInfoCompatWrapper> imiList) {
+        final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+        if (imm == null) return false;
 
         // Number of the filtered IMEs
         int filteredImisCount = 0;
 
-        for (InputMethodInfoCompatWrapper imi : enabledImis) {
+        for (InputMethodInfoCompatWrapper imi : imiList) {
             // We can return true immediately after we find two or more filtered IMEs.
             if (filteredImisCount > 1) return true;
             final List<InputMethodSubtypeCompatWrapper> subtypes =
@@ -564,6 +600,7 @@ public class Utils {
         }
     }
 
+    // TODO: Move this method to KeyboardSet class.
     public static int getKeyboardMode(EditorInfo editorInfo) {
         if (editorInfo == null)
             return KeyboardId.MODE_TEXT;