Merge "Promote a word with only one proximity character."
diff --git a/java/res/anim/key_preview_fadein.xml b/java/res/anim/key_preview_fadein.xml
deleted file mode 100644
index 9fad7b9..0000000
--- a/java/res/anim/key_preview_fadein.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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.
-*/
--->
-
-<set
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:anim/decelerate_interpolator"
->
-    <alpha
-        android:fromAlpha="0.5"
-        android:toAlpha="1.0"
-        android:duration="@integer/config_preview_fadein_anim_time" />
-</set>
diff --git a/java/res/anim/key_preview_fadeout.xml b/java/res/anim/key_preview_fadeout.xml
deleted file mode 100644
index 7de5123..0000000
--- a/java/res/anim/key_preview_fadeout.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2010, 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.
-*/
--->
-
-<set
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:interpolator="@android:anim/accelerate_interpolator"
->
-    <alpha
-        android:fromAlpha="1.0"
-        android:toAlpha="0.0"
-        android:duration="@integer/config_preview_fadeout_anim_time" />
-</set>
diff --git a/java/res/anim/mini_keyboard_fadein.xml b/java/res/anim/mini_keyboard_fadein.xml
index 9fad7b9..f80e8b8 100644
--- a/java/res/anim/mini_keyboard_fadein.xml
+++ b/java/res/anim/mini_keyboard_fadein.xml
@@ -25,5 +25,5 @@
     <alpha
         android:fromAlpha="0.5"
         android:toAlpha="1.0"
-        android:duration="@integer/config_preview_fadein_anim_time" />
+        android:duration="@integer/config_mini_keyboard_fadein_anim_time" />
 </set>
diff --git a/java/res/anim/mini_keyboard_fadeout.xml b/java/res/anim/mini_keyboard_fadeout.xml
index 7de5123..535b100 100644
--- a/java/res/anim/mini_keyboard_fadeout.xml
+++ b/java/res/anim/mini_keyboard_fadeout.xml
@@ -25,5 +25,5 @@
     <alpha
         android:fromAlpha="1.0"
         android:toAlpha="0.0"
-        android:duration="@integer/config_preview_fadeout_anim_time" />
+        android:duration="@integer/config_mini_keyboard_fadeout_anim_time" />
 </set>
diff --git a/java/res/layout-xlarge/candidates.xml b/java/res/layout-xlarge/candidates.xml
index d10035c..9d6cd24 100644
--- a/java/res/layout-xlarge/candidates.xml
+++ b/java/res/layout-xlarge/candidates.xml
@@ -21,25 +21,33 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
+    android:gravity="bottom"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/keyboard_suggest_strip_holo"
-    android:paddingRight="@dimen/candidate_strip_padding"
-    android:paddingLeft="@dimen/candidate_strip_padding"
+    android:minHeight="@dimen/candidate_strip_minimum_height"
 >
-    <HorizontalScrollView
-        android:id="@+id/candidates_scroll_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:fadingEdge="horizontal"
-        android:fadingEdgeLength="@dimen/candidate_strip_fading_edge_length"
-        android:scrollbars="none"
+    <!-- On tablets, the candidate strip is centered with horizontal paddings on both sides because
+         width of the landscape mode is too long for the candidate strip. This LinearLayout is
+         required to hold the paddings. -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/candidate_strip_height"
+        android:background="@drawable/keyboard_suggest_strip_holo"
+        android:paddingRight="@dimen/candidate_strip_padding"
+        android:paddingLeft="@dimen/candidate_strip_padding"
     >
-        <com.android.inputmethod.latin.CandidateView
-            android:id="@+id/candidates"
-            android:orientation="horizontal"
+        <HorizontalScrollView
             android:layout_width="match_parent"
-            android:layout_height="@dimen/candidate_strip_height"
-            android:background="@drawable/keyboard_suggest_strip_holo" />
-    </HorizontalScrollView>
+            android:layout_height="match_parent"
+            android:fadingEdge="horizontal"
+            android:fadingEdgeLength="@dimen/candidate_strip_fading_edge_length"
+            android:scrollbars="none"
+        >
+            <com.android.inputmethod.latin.CandidateView
+                android:id="@+id/candidates"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" />
+        </HorizontalScrollView>
+    </LinearLayout>
 </LinearLayout>
diff --git a/java/res/layout/candidates.xml b/java/res/layout/candidates.xml
index 794c4ed..39e39a1 100644
--- a/java/res/layout/candidates.xml
+++ b/java/res/layout/candidates.xml
@@ -21,25 +21,24 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
+    android:gravity="bottom"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/keyboard_suggest_strip"
+    android:minHeight="@dimen/candidate_strip_minimum_height"
     android:paddingRight="@dimen/candidate_strip_padding"
     android:paddingLeft="@dimen/candidate_strip_padding"
 >
     <HorizontalScrollView
-        android:id="@+id/candidates_scroll_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/candidate_strip_height"
+        android:background="@drawable/keyboard_suggest_strip"
         android:fadingEdge="horizontal"
         android:fadingEdgeLength="@dimen/candidate_strip_fading_edge_length"
         android:scrollbars="none"
     >
         <com.android.inputmethod.latin.CandidateView
             android:id="@+id/candidates"
-            android:orientation="horizontal"
             android:layout_width="match_parent"
-            android:layout_height="@dimen/candidate_strip_height"
-            android:background="@drawable/keyboard_suggest_strip" />
+            android:layout_height="match_parent" />
     </HorizontalScrollView>
 </LinearLayout>
diff --git a/java/res/values-xlarge/dimens.xml b/java/res/values-xlarge/dimens.xml
index 0dc5621..9bb50f6 100644
--- a/java/res/values-xlarge/dimens.xml
+++ b/java/res/values-xlarge/dimens.xml
@@ -46,6 +46,9 @@
     <dimen name="key_preview_offset_holo">8.0mm</dimen>
 
     <dimen name="candidate_strip_height">46dip</dimen>
+    <!-- candidate_strip_minimum_height =
+         key_preview_height_holo - key_preview_offset_holo + alpha -->
+    <dimen name="candidate_strip_minimum_height">18mm</dimen>
     <dimen name="candidate_strip_padding">15.0mm</dimen>
     <dimen name="candidate_min_width">0.3in</dimen>
     <dimen name="candidate_padding">12dip</dimen>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index bdb4409..32fb5bf 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -49,8 +49,6 @@
     <integer name="config_final_fadeout_percentage_of_language_on_spacebar">15</integer>
     <integer name="config_delay_before_preview">0</integer>
     <integer name="config_delay_after_preview">10</integer>
-    <integer name="config_preview_fadein_anim_time">0</integer>
-    <integer name="config_preview_fadeout_anim_time">70</integer>
     <integer name="config_mini_keyboard_fadein_anim_time">0</integer>
     <integer name="config_mini_keyboard_fadeout_anim_time">100</integer>
     <integer name="config_delay_before_key_repeat_start">400</integer>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 767dc4a..0a0b16d 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -52,6 +52,9 @@
     <dimen name="key_preview_offset_holo">0.193in</dimen>
 
     <dimen name="candidate_strip_height">42dip</dimen>
+    <!-- candidate_strip_minimum_height =
+         key_preview_height_holo - key_preview_offset_holo + alpha -->
+    <dimen name="candidate_strip_minimum_height">100sp</dimen>
     <dimen name="candidate_strip_fading_edge_length">63dip</dimen>
     <dimen name="candidate_strip_padding">0dip</dimen>
     <dimen name="candidate_min_width">0.3in</dimen>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 130714f..ef48a4b 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -35,10 +35,6 @@
         <item name="backgroundDimAmount">0.5</item>
         <item name="colorScheme">white</item>
     </style>
-    <style name="KeyPreviewAnimation">
-        <item name="android:windowEnterAnimation">@anim/key_preview_fadein</item>
-        <item name="android:windowExitAnimation">@anim/key_preview_fadeout</item>
-    </style>
     <style name="MiniKeyboardAnimation">
         <item name="android:windowEnterAnimation">@anim/mini_keyboard_fadein</item>
         <item name="android:windowExitAnimation">@anim/mini_keyboard_fadeout</item>
diff --git a/java/res/xml/kbd_ar_rows.xml b/java/res/xml/kbd_ar_rows.xml
index 4b490f1..d5264fd 100644
--- a/java/res/xml/kbd_ar_rows.xml
+++ b/java/res/xml/kbd_ar_rows.xml
@@ -51,8 +51,8 @@
         <Key
             latin:keyLabel="ح" />
         <Key
-            latin:keyLabel="چ"
-            latin:popupCharacters="ج"
+            latin:keyLabel="ج"
+            latin:popupCharacters="چ"
             latin:keyEdgeFlags="right" />
     </Row>
     <Row
@@ -65,7 +65,7 @@
             latin:keyLabel="س" />
         <Key
             latin:keyLabel="ي"
-            latin:popupCharacters="ئ,ى" />
+            latin:popupCharacters="ى,ئ" />
         <Key
             latin:keyLabel="ب"
             latin:popupCharacters="پ" />
@@ -74,7 +74,7 @@
             latin:popupCharacters="لا" />
         <Key
             latin:keyLabel="ا"
-            latin:popupCharacters="ء,آ,إ,أ" />
+            latin:popupCharacters="أ,إ,آ,ء" />
         <Key
             latin:keyLabel="ت" />
         <Key
diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
index 80586b7..e7eaba2 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.compat;
 
+import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.Utils;
@@ -56,6 +57,14 @@
     private static final InputMethodManagerCompatWrapper sInstance =
             new InputMethodManagerCompatWrapper();
 
+    public static final boolean SUBTYPE_SUPPORTED;
+
+    static {
+        // This static initializer guarantees that METHOD_getShortcutInputMethodsAndSubtypes is
+        // already instantiated.
+        SUBTYPE_SUPPORTED = METHOD_getShortcutInputMethodsAndSubtypes != null;
+    }
+
     // For the compatibility, IMM will create dummy subtypes if subtypes are not found.
     // This is required to be false if the current behavior is broken. For now, it's ok to be true.
     private static final boolean ALLOW_DUMMY_SUBTYPE = true;
@@ -64,7 +73,9 @@
     private static final String KEYBOARD_MODE = "keyboard";
 
     private InputMethodManager mImm;
+    private LanguageSwitcherProxy mLanguageSwitcherProxy;
     private String mLatinImePackageName;
+
     private InputMethodManagerCompatWrapper() {
     }
 
@@ -81,15 +92,29 @@
         if (context instanceof LatinIME) {
             mLatinImePackageName = context.getPackageName();
         }
+        mLanguageSwitcherProxy = LanguageSwitcherProxy.getInstance();
     }
 
     public InputMethodSubtypeCompatWrapper getCurrentInputMethodSubtype() {
+        if (!SUBTYPE_SUPPORTED) {
+            return new InputMethodSubtypeCompatWrapper(
+                    0, 0, mLanguageSwitcherProxy.getInputLocale().toString(), KEYBOARD_MODE, "");
+        }
         Object o = CompatUtils.invoke(mImm, null, METHOD_getCurrentInputMethodSubtype);
         return new InputMethodSubtypeCompatWrapper(o);
     }
 
     public List<InputMethodSubtypeCompatWrapper> getEnabledInputMethodSubtypeList(
             InputMethodInfoCompatWrapper imi, boolean allowsImplicitlySelectedSubtypes) {
+        if (!SUBTYPE_SUPPORTED) {
+            String[] languages = mLanguageSwitcherProxy.getEnabledLanguages();
+            List<InputMethodSubtypeCompatWrapper> subtypeList =
+                    new ArrayList<InputMethodSubtypeCompatWrapper>();
+            for (String lang: languages) {
+                subtypeList.add(new InputMethodSubtypeCompatWrapper(0, 0, lang, KEYBOARD_MODE, ""));
+            }
+            return subtypeList;
+        }
         Object retval = CompatUtils.invoke(mImm, null, METHOD_getEnabledInputMethodSubtypeList,
                 (imi != null ? imi.getInputMethodInfo() : null), allowsImplicitlySelectedSubtypes);
         if (retval == null || !(retval instanceof List) || ((List<?>)retval).isEmpty()) {
@@ -170,6 +195,10 @@
 
     public void setInputMethodAndSubtype(
             IBinder token, String id, InputMethodSubtypeCompatWrapper subtype) {
+        if (!SUBTYPE_SUPPORTED) {
+            mLanguageSwitcherProxy.setLocale(subtype.getLocale());
+            return;
+        }
         CompatUtils.invoke(mImm, null, METHOD_setInputMethodAndSubtype,
                 token, id, subtype.getOriginalObject());
     }
diff --git a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
index e02aac7..1ea7236 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodServiceCompatWrapper.java
@@ -19,20 +19,9 @@
 import com.android.inputmethod.latin.SubtypeSwitcher;
 
 import android.inputmethodservice.InputMethodService;
-import android.view.View;
 import android.view.inputmethod.InputMethodSubtype;
-import android.widget.HorizontalScrollView;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
 
 public class InputMethodServiceCompatWrapper extends InputMethodService {
-    private static final Method METHOD_HorizontalScrollView_setOverScrollMode =
-            CompatUtils.getMethod(HorizontalScrollView.class, "setOverScrollMode", int.class);
-    private static final Field FIELD_View_OVER_SCROLL_NEVER =
-            CompatUtils.getField(View.class, "OVER_SCROLL_NEVER");
-    private static final Integer View_OVER_SCROLL_NEVER =
-            (Integer)CompatUtils.getFieldValue(null, null, FIELD_View_OVER_SCROLL_NEVER);
     // CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED needs to be false if the API level is 10
     // or previous. Note that InputMethodSubtype was added in the API level 11.
     // For the API level 11 or later, LatinIME should override onCurrentInputMethodSubtypeChanged().
@@ -66,13 +55,6 @@
         }
     }
 
-    protected static void setOverScrollModeNever(HorizontalScrollView scrollView) {
-        if (View_OVER_SCROLL_NEVER != null) {
-            CompatUtils.invoke(scrollView, null, METHOD_HorizontalScrollView_setOverScrollMode,
-                    View_OVER_SCROLL_NEVER);
-        }
-    }
-
     //////////////////////////////////////
     // Functions using API v11 or later //
     //////////////////////////////////////
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
index 317b022..86c8af3 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatWrapper.java
@@ -58,9 +58,6 @@
     public InputMethodSubtypeCompatWrapper(Object subtype) {
         super((CLASS_InputMethodSubtype != null && CLASS_InputMethodSubtype.isInstance(subtype))
                 ? subtype : null);
-        if (DBG) {
-            Log.d(TAG, "CreateInputMethodSubtypeCompatWrapper");
-        }
         mDummyNameResId = 0;
         mDummyIconResId = 0;
         mDummyLocale = DEFAULT_LOCALE;
diff --git a/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java b/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java
new file mode 100644
index 0000000..5d165cd
--- /dev/null
+++ b/java/src/com/android/inputmethod/deprecated/LanguageSwitcherProxy.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * 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.deprecated;
+
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.deprecated.languageswitcher.LanguageSwitcher;
+import com.android.inputmethod.latin.LatinIME;
+
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+
+import java.util.Locale;
+
+// This class is used only when the IME doesn't use method.xml for language switching.
+public class LanguageSwitcherProxy {
+    private static final LanguageSwitcherProxy sInstance = new LanguageSwitcherProxy();
+    private LanguageSwitcher mLanguageSwitcher;
+    private SharedPreferences mPrefs;
+
+    public static LanguageSwitcherProxy getInstance() {
+        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return null;
+        return sInstance;
+    }
+
+    public static void init(LatinIME service, SharedPreferences prefs) {
+        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
+        final Configuration conf = service.getResources().getConfiguration();
+        sInstance.mLanguageSwitcher = new LanguageSwitcher(service);
+        sInstance.mLanguageSwitcher.loadLocales(prefs, conf.locale);
+        sInstance.mPrefs = prefs;
+    }
+
+    public static void onConfigurationChanged(Configuration conf) {
+        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
+        sInstance.mLanguageSwitcher.onConfigurationChanged(conf, sInstance.mPrefs);
+    }
+
+    public static void loadSettings() {
+        if (InputMethodManagerCompatWrapper.SUBTYPE_SUPPORTED) return;
+        sInstance.mLanguageSwitcher.loadLocales(sInstance.mPrefs, null);
+    }
+
+    public int getLocaleCount() {
+        return mLanguageSwitcher.getLocaleCount();
+    }
+
+    public String[] getEnabledLanguages() {
+        return mLanguageSwitcher.getEnabledLanguages();
+    }
+
+    public Locale getInputLocale() {
+        return mLanguageSwitcher.getInputLocale();
+    }
+
+    public void setLocale(String localeStr) {
+        mLanguageSwitcher.setLocale(localeStr);
+        mLanguageSwitcher.persist(mPrefs);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
similarity index 76%
rename from java/src/com/android/inputmethod/latin/LanguageSwitcher.java
rename to java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
index 6faf7f9..639b7cd 100644
--- a/java/src/com/android/inputmethod/latin/LanguageSwitcher.java
+++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
@@ -14,10 +14,16 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.deprecated.languageswitcher;
+
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.Settings;
+import com.android.inputmethod.latin.SharedPreferencesCompat;
 
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
 import android.text.TextUtils;
 
 import java.util.ArrayList;
@@ -29,6 +35,8 @@
  */
 public class LanguageSwitcher {
 
+    private static final String KEYBOARD_MODE = "keyboard";
+
     private final ArrayList<Locale> mLocales = new ArrayList<Locale>();
     private final LatinIME mIme;
     private String[] mSelectedLanguageArray;
@@ -46,12 +54,24 @@
         return mLocales.size();
     }
 
+    public void onConfigurationChanged(Configuration conf, SharedPreferences prefs) {
+        final Locale newLocale = conf.locale;
+        if (!getSystemLocale().toString().equals(newLocale.toString())) {
+            loadLocales(prefs, newLocale);
+        }
+    }
+
     /**
      * Loads the currently selected input languages from shared preferences.
-     * @param sp
+     * @param sp shared preference for getting the current input language and enabled languages
+     * @param systemLocale the current system locale, stored for changing the current input language
+     * based on the system current system locale.
      * @return whether there was any change
      */
-    public boolean loadLocales(SharedPreferences sp) {
+    public boolean loadLocales(SharedPreferences sp, Locale systemLocale) {
+        if (systemLocale != null) {
+            setSystemLocale(systemLocale);
+        }
         String selectedLanguages = sp.getString(Settings.PREF_SELECTED_LANGUAGES, null);
         String currentLanguage   = sp.getString(Settings.PREF_INPUT_LANGUAGE, null);
         if (selectedLanguages == null || selectedLanguages.length() < 1) {
@@ -151,7 +171,7 @@
      * Sets the system locale (display UI) used for comparing with the input language.
      * @param locale the locale of the system
      */
-    public void setSystemLocale(Locale locale) {
+    private void setSystemLocale(Locale locale) {
         mSystemLocale = locale;
     }
 
@@ -159,7 +179,7 @@
      * Returns the system locale.
      * @return the system locale
      */
-    public Locale getSystemLocale() {
+    private Locale getSystemLocale() {
         return mSystemLocale;
     }
 
@@ -185,9 +205,22 @@
         mCurrentIndex = prevLocaleIndex();
     }
 
+    public void setLocale(String localeStr) {
+        final int N = mLocales.size();
+        for (int i = 0; i < N; ++i) {
+            if (mLocales.get(i).toString().equals(localeStr)) {
+                mCurrentIndex = i;
+            }
+        }
+    }
+
     public void persist(SharedPreferences prefs) {
         Editor editor = prefs.edit();
         editor.putString(Settings.PREF_INPUT_LANGUAGE, getInputLanguage());
         SharedPreferencesCompat.apply(editor);
+        // When the current language is changed, the event for this change should be handled
+        // internally as a subtype switching.
+        mIme.notifyOnCurrentInputMethodSubtypeChanged(new InputMethodSubtypeCompatWrapper(
+                0, 0, getInputLocale().toString(), KEYBOARD_MODE, ""));
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 7b570d7..7186380 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -45,8 +45,8 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.PopupWindow;
 import android.widget.TextView;
 
@@ -110,14 +110,12 @@
     // Key preview popup
     private boolean mInForeground;
     private TextView mPreviewText;
-    private PopupWindow mPreviewPopup;
     private int mPreviewTextSizeLarge;
-    private int[] mOffsetInWindow;
+    private final int[] mOffsetInWindow = new int[2];
     private int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY;
     private boolean mShowPreview = true;
     private int mPopupPreviewOffsetX;
     private int mPopupPreviewOffsetY;
-    private int mWindowY;
     private int mPopupPreviewDisplayedY;
     private final int mDelayBeforePreview;
     private final int mDelayAfterPreview;
@@ -125,7 +123,6 @@
     // Popup mini keyboard
     private PopupWindow mMiniKeyboardPopup;
     private KeyboardView mMiniKeyboardView;
-    private View mMiniKeyboardParent;
     private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>();
     private int mMiniKeyboardOriginX;
     private int mMiniKeyboardOriginY;
@@ -204,7 +201,7 @@
                     showKey(msg.arg1, (PointerTracker)msg.obj);
                     break;
                 case MSG_DISMISS_PREVIEW:
-                    mPreviewPopup.dismiss();
+                    mPreviewText.setVisibility(View.INVISIBLE);
                     break;
                 case MSG_REPEAT_KEY: {
                     final PointerTracker tracker = (PointerTracker)msg.obj;
@@ -227,12 +224,11 @@
 
         public void popupPreview(long delay, int keyIndex, PointerTracker tracker) {
             removeMessages(MSG_POPUP_PREVIEW);
-            if (mPreviewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+            if (mPreviewText.getVisibility() == VISIBLE) {
                 // Show right away, if it's already visible and finger is moving around
                 showKey(keyIndex, tracker);
             } else {
-                sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker),
-                        delay);
+                sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker), delay);
             }
         }
 
@@ -241,9 +237,7 @@
         }
 
         public void dismissPreview(long delay) {
-            if (mPreviewPopup.isShowing()) {
-                sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
-            }
+            sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
         }
 
         public void cancelDismissPreview() {
@@ -366,24 +360,17 @@
 
         final Resources res = getResources();
 
-        mPreviewPopup = new PopupWindow(context);
         if (previewLayout != 0) {
             mPreviewText = (TextView) LayoutInflater.from(context).inflate(previewLayout, null);
             mPreviewTextSizeLarge = (int) res.getDimension(R.dimen.key_preview_text_size_large);
-            mPreviewPopup.setContentView(mPreviewText);
-            mPreviewPopup.setBackgroundDrawable(null);
         } else {
             mShowPreview = false;
         }
-        mPreviewPopup.setTouchable(false);
-        mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation);
-        mPreviewPopup.setClippingEnabled(false);
         mDelayBeforePreview = res.getInteger(R.integer.config_delay_before_preview);
         mDelayAfterPreview = res.getInteger(R.integer.config_delay_after_preview);
         mKeyLabelHorizontalPadding = (int)res.getDimension(
                 R.dimen.key_label_horizontal_alignment_padding);
 
-        mMiniKeyboardParent = this;
         mMiniKeyboardPopup = new PopupWindow(context);
         mMiniKeyboardPopup.setBackgroundDrawable(null);
         mMiniKeyboardPopup.setAnimationStyle(R.style.MiniKeyboardAnimation);
@@ -583,7 +570,6 @@
     public void setPopupOffset(int x, int y) {
         mPopupPreviewOffsetX = x;
         mPopupPreviewOffsetY = y;
-        mPreviewPopup.dismiss();
     }
 
     /**
@@ -915,8 +901,16 @@
         }
     }
 
-    // TODO Must fix popup preview on xlarge layout
+    // TODO: Introduce minimum duration for displaying key previews
+    // TODO: Display up to two key previews when the user presses two keys at the same time
     private void showKey(final int keyIndex, PointerTracker tracker) {
+        // If the preview popup has no parent view yet, add it to the screen FrameLayout.
+        if (mPreviewText.getParent() == null) {
+            final FrameLayout screenContent = (FrameLayout) getRootView()
+                    .findViewById(android.R.id.content);
+            screenContent.addView(mPreviewText, new FrameLayout.LayoutParams(0, 0));
+        }
+
         Key key = tracker.getKey(keyIndex);
         // If keyIndex is invalid or IME is already closed, we must not show key preview.
         // Trying to show preview PopupWindow while root window is closed causes
@@ -948,7 +942,7 @@
         int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), keyDrawWidth
                 + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
         final int popupHeight = mPreviewHeight;
-        LayoutParams lp = mPreviewText.getLayoutParams();
+        final ViewGroup.LayoutParams lp = mPreviewText.getLayoutParams();
         if (lp != null) {
             lp.width = popupWidth;
             lp.height = popupHeight;
@@ -958,47 +952,23 @@
         int popupPreviewY = key.mY - popupHeight + mPreviewOffset;
 
         mHandler.cancelDismissPreview();
-        if (mOffsetInWindow == null) {
-            mOffsetInWindow = new int[2];
-            getLocationInWindow(mOffsetInWindow);
-            mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero
-            mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero
-            int[] windowLocation = new int[2];
-            getLocationOnScreen(windowLocation);
-            mWindowY = windowLocation[1];
-        }
+        getLocationInWindow(mOffsetInWindow);
+        mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero
+        mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero
+
         // Set the preview background state
         mPreviewText.getBackground().setState(
                 key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
         popupPreviewX += mOffsetInWindow[0];
         popupPreviewY += mOffsetInWindow[1];
 
-        // If the popup cannot be shown above the key, put it on the side
-        if (popupPreviewY + mWindowY < 0) {
-            // If the key you're pressing is on the left side of the keyboard, show the popup on
-            // the right, offset by enough to see at least one key to the left/right.
-            if (keyDrawX + keyDrawWidth <= getWidth() / 2) {
-                popupPreviewX += (int) (keyDrawWidth * 2.5);
-            } else {
-                popupPreviewX -= (int) (keyDrawWidth * 2.5);
-            }
-            popupPreviewY += popupHeight;
+        // Place the key preview.
+        // TODO: Adjust position of key previews which touch screen edges
+        if (lp instanceof ViewGroup.MarginLayoutParams) {
+            ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)lp;
+            mlp.setMargins(popupPreviewX, popupPreviewY, 0, 0);
         }
-
-        try {
-            if (mPreviewPopup.isShowing()) {
-                mPreviewPopup.update(popupPreviewX, popupPreviewY, popupWidth, popupHeight);
-            } else {
-                mPreviewPopup.setWidth(popupWidth);
-                mPreviewPopup.setHeight(popupHeight);
-                mPreviewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY,
-                        popupPreviewX, popupPreviewY);
-            }
-        } catch (WindowManager.BadTokenException e) {
-            // Swallow the exception which will be happened when IME is already closed.
-            Log.w(TAG, "LatinIME is already closed when tried showing key preview.");
-        }
-        // Record popup preview position to display mini-keyboard later at the same positon
+        // Record popup preview position to display mini-keyboard later at the same position
         mPopupPreviewDisplayedY = popupPreviewY;
         mPreviewText.setVisibility(VISIBLE);
     }
@@ -1114,7 +1084,6 @@
         final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(),
                 popupKey).build();
         miniKeyboardView.setKeyboard(keyboard);
-        miniKeyboardView.mMiniKeyboardParent = this;
 
         container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
                 MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
@@ -1349,7 +1318,7 @@
     }
 
     public void closing() {
-        mPreviewPopup.dismiss();
+        mPreviewText.setVisibility(View.GONE);
         mHandler.cancelAllMessages();
 
         dismissPopupKeyboard();
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 9b87df3..9d9793e 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -271,7 +271,7 @@
             canvas.drawText(language, width / 2, baseline - descent, paint);
 
             // Put arrows that are already layed out on either side of the text
-            if (SubtypeSwitcher.getInstance().useSpacebarLanguageSwitcher()
+            if (subtypeSwitcher.useSpacebarLanguageSwitcher()
                     && subtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) {
                 mButtonArrowLeftIcon.draw(canvas);
                 mButtonArrowRightIcon.draw(canvas);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index eb5335f..add38cf 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -549,7 +549,6 @@
         final Key key = getKey(keyIndex);
         if (key != null && !key.mEnabled)
             return;
-        updateKeyGraphics(keyIndex);
         // The modifier key, such as shift key, should not be shown as preview when multi-touch is
         // supported. On the other hand, if multi-touch is not supported, the modifier key should
         // be shown as preview. If accessibility is turned on, the modifier key should be shown as
@@ -559,6 +558,7 @@
         } else {
             mProxy.showPreview(keyIndex, this);
         }
+        updateKeyGraphics(keyIndex);
     }
 
     private void startLongPressTimer(int keyIndex) {
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 5719b90..6fb80ad 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -133,7 +133,6 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT);
         mPreviewPopup.setContentView(mPreviewText);
         mPreviewPopup.setBackgroundDrawable(null);
-        mPreviewPopup.setAnimationStyle(R.style.KeyPreviewAnimation);
         mConfigCandidateHighlightFontColorEnabled =
                 res.getBoolean(R.bool.config_candidate_highlight_font_color_enabled);
         mColorNormal = res.getColor(R.color.candidate_normal);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6a858fe..d87de09 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -23,6 +23,7 @@
 import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
 import com.android.inputmethod.compat.InputTypeCompatUtils;
 import com.android.inputmethod.compat.VibratorCompatWrapper;
+import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
 import com.android.inputmethod.deprecated.VoiceProxy;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
@@ -56,7 +57,6 @@
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
-import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -70,8 +70,6 @@
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 
 import java.io.FileDescriptor;
@@ -143,6 +141,7 @@
     };
 
     private View mCandidateViewContainer;
+    private int mCandidateStripHeight;
     private CandidateView mCandidateView;
     private Suggest mSuggest;
     private CompletionInfo[] mApplicationSpecifiedCompletions;
@@ -379,6 +378,7 @@
         SubtypeSwitcher.init(this, prefs);
         KeyboardSwitcher.init(this, prefs);
         AccessibilityUtils.init(this, prefs);
+        LanguageSwitcherProxy.init(this, prefs);
 
         super.onCreate();
 
@@ -516,6 +516,9 @@
         super.onConfigurationChanged(conf);
         mVoiceProxy.onConfigurationChanged(conf);
         mConfigurationChanging = false;
+
+        // This will work only when the subtype is not supported.
+        LanguageSwitcherProxy.onConfigurationChanged(conf);
     }
 
     @Override
@@ -528,12 +531,7 @@
         LayoutInflater inflater = getLayoutInflater();
         LinearLayout container = (LinearLayout)inflater.inflate(R.layout.candidates, null);
         mCandidateViewContainer = container;
-        if (container.getPaddingRight() != 0) {
-            HorizontalScrollView scrollView =
-                    (HorizontalScrollView) container.findViewById(R.id.candidates_scroll_view);
-            setOverScrollModeNever(scrollView);
-            container.setGravity(Gravity.CENTER_HORIZONTAL);
-        }
+        mCandidateStripHeight = (int)mResources.getDimension(R.dimen.candidate_strip_height);
         mCandidateView = (CandidateView) container.findViewById(R.id.candidates);
         mCandidateView.setService(this);
         setCandidatesViewShown(true);
@@ -581,8 +579,7 @@
             switcher.updateShiftState();
         }
 
-        setCandidatesViewShownInternal(isCandidateStripVisible(),
-                false /* needsInputViewShown */ );
+        setCandidatesViewShownInternal(isCandidateStripVisible(), false /* needsInputViewShown */ );
         // Delay updating suggestions because keyboard input view may not be shown at this point.
         mHandler.postUpdateSuggestions();
 
@@ -872,10 +869,21 @@
     }
 
     private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
-        // TODO: Remove this if we support candidates with hard keyboard
+        // TODO: Modify this if we support candidates with hard keyboard
         if (onEvaluateInputViewShown()) {
-            super.setCandidatesViewShown(shown
-                    && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true));
+            final boolean shouldShowCandidates = shown
+                    && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true);
+            if (isExtractViewShown()) {
+                // No need to have extra space to show the key preview.
+                mCandidateViewContainer.setMinimumHeight(0);
+                super.setCandidatesViewShown(shown);
+            } else {
+                // We must control the visibility of the suggestion strip in order to avoid clipped
+                // key previews, even when we don't show the suggestion strip.
+                mCandidateViewContainer.setVisibility(
+                        shouldShowCandidates ? View.VISIBLE : View.INVISIBLE);
+                super.setCandidatesViewShown(true);
+            }
         }
     }
 
@@ -887,35 +895,25 @@
     @Override
     public void onComputeInsets(InputMethodService.Insets outInsets) {
         super.onComputeInsets(outInsets);
-        if (!isFullscreenMode()) {
-            outInsets.contentTopInsets = outInsets.visibleTopInsets;
-        }
-        KeyboardView inputView = mKeyboardSwitcher.getInputView();
+        final KeyboardView inputView = mKeyboardSwitcher.getInputView();
         // Need to set touchable region only if input view is being shown
         if (inputView != null && mKeyboardSwitcher.isInputViewShown()) {
-            final int x = 0;
-            int y = 0;
-            final int width = inputView.getWidth();
-            int height = inputView.getHeight() + EXTENDED_TOUCHABLE_REGION_HEIGHT;
-            if (mCandidateViewContainer != null) {
-                ViewParent candidateParent = mCandidateViewContainer.getParent();
-                if (candidateParent instanceof FrameLayout) {
-                    FrameLayout fl = (FrameLayout) candidateParent;
-                    if (fl != null) {
-                        // Check frame layout's visibility
-                        if (fl.getVisibility() == View.INVISIBLE) {
-                            y = fl.getHeight();
-                            height += y;
-                        } else if (fl.getVisibility() == View.VISIBLE) {
-                            height += fl.getHeight();
-                        }
-                    }
-                }
+            final int containerHeight = mCandidateViewContainer.getHeight();
+            int touchY = containerHeight;
+            if (mCandidateViewContainer.getVisibility() == View.VISIBLE) {
+                touchY -= mCandidateStripHeight;
             }
+            outInsets.contentTopInsets = touchY;
+            outInsets.visibleTopInsets = touchY;
+            final int touchWidth = inputView.getWidth();
+            final int touchHeight = inputView.getHeight() + containerHeight
+                    // Extend touchable region below the keyboard.
+                    + EXTENDED_TOUCHABLE_REGION_HEIGHT;
             if (DEBUG) {
-                Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height);
+                Log.d(TAG, "Touchable region: y=" + touchY + " width=" + touchWidth
+                        + " height=" + touchHeight);
             }
-            setTouchableRegionCompat(outInsets, x, y, width, height);
+            setTouchableRegionCompat(outInsets, 0, touchY, touchWidth, touchHeight);
         }
     }
 
@@ -1155,10 +1153,10 @@
             onSettingsKeyLongPressed();
             break;
         case Keyboard.CODE_NEXT_LANGUAGE:
-            toggleLanguage(false, true);
+            toggleLanguage(true);
             break;
         case Keyboard.CODE_PREV_LANGUAGE:
-            toggleLanguage(false, false);
+            toggleLanguage(false);
             break;
         case Keyboard.CODE_CAPSLOCK:
             switcher.toggleCapsLock();
@@ -1930,17 +1928,9 @@
         return mWord.isFirstCharCapitalized();
     }
 
-    // Notify that language or mode have been changed and toggleLanguage will update KeyboaredID
+    // Notify that language or mode have been changed and toggleLanguage will update KeyboardID
     // according to new language or mode.
     public void onRefreshKeyboard() {
-        toggleLanguage(true, true);
-    }
-
-    // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER.
-    private void toggleLanguage(boolean reset, boolean next) {
-        if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) {
-            mSubtypeSwitcher.toggleLanguage(reset, next);
-        }
         // Reload keyboard because the current language has been changed.
         mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(),
                 mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceProxy.isVoiceButtonEnabled(),
@@ -1949,6 +1939,14 @@
         mKeyboardSwitcher.updateShiftState();
     }
 
+    // "reset" and "next" are used only for USE_SPACEBAR_LANGUAGE_SWITCHER.
+    private void toggleLanguage(boolean next) {
+        if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()) {
+            mSubtypeSwitcher.toggleLanguage(next);
+        }
+        onRefreshKeyboard();// no need??
+    }
+
     @Override
     public void onSwipeDown() {
         if (mConfigSwipeDownDismissKeyboardEnabled)
@@ -2134,7 +2132,9 @@
         updateCorrectionMode();
         updateAutoTextEnabled();
         updateSuggestionVisibility(prefs);
-        SubtypeSwitcher.getInstance().loadSettings();
+
+        // This will work only when the subtype is not supported.
+        LanguageSwitcherProxy.loadSettings();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 053e2ab..2cdc4d2 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -57,7 +57,6 @@
 
     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
     private /* final */ LatinIME mService;
-    private /* final */ SharedPreferences mPrefs;
     private /* final */ InputMethodManagerCompatWrapper mImm;
     private /* final */ Resources mResources;
     private /* final */ ConnectivityManager mConnectivityManager;
@@ -66,6 +65,7 @@
             mEnabledKeyboardSubtypesOfCurrentInputMethod =
                     new ArrayList<InputMethodSubtypeCompatWrapper>();
     private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
+    private final LanguageBarInfo mLanguageBarInfo = new LanguageBarInfo();
 
     /*-----------------------------------------------------------*/
     // Variants which should be changed only by reload functions.
@@ -78,6 +78,7 @@
     private Locale mSystemLocale;
     private Locale mInputLocale;
     private String mInputLocaleStr;
+    private String mInputMethodId;
     private VoiceProxy.VoiceInputWrapper mVoiceInputWrapper;
     /*-----------------------------------------------------------*/
 
@@ -100,7 +101,6 @@
 
     private void initialize(LatinIME service, SharedPreferences prefs) {
         mService = service;
-        mPrefs = prefs;
         mResources = service.getResources();
         mImm = InputMethodManagerCompatWrapper.getInstance(service);
         mConnectivityManager = (ConnectivityManager) service.getSystemService(
@@ -114,13 +114,12 @@
         mAllEnabledSubtypesOfCurrentInputMethod = null;
         // TODO: Voice input should be created here
         mVoiceInputWrapper = null;
-        mConfigUseSpacebarLanguageSwitcher = mResources.getBoolean(
+        mConfigUseSpacebarLanguageSwitcher = service.getResources().getBoolean(
                 R.bool.config_use_spacebar_language_switcher);
-        if (mConfigUseSpacebarLanguageSwitcher)
-            initLanguageSwitcher(service);
 
         final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
         mIsNetworkConnected = (info != null && info.isConnected());
+        mInputMethodId = Utils.getInputMethodId(mImm, service.getPackageName());
     }
 
     // Update all parameters stored in SubtypeSwitcher.
@@ -134,11 +133,7 @@
     // Update parameters which are changed outside LatinIME. This parameters affect UI so they
     // should be updated every time onStartInputview.
     public void updateParametersOnStartInputView() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            updateForSpacebarLanguageSwitch();
-        } else {
-            updateEnabledSubtypes();
-        }
+        updateEnabledSubtypes();
         updateShortcutIME();
     }
 
@@ -150,7 +145,7 @@
                 null, true);
         mEnabledLanguagesOfCurrentInputMethod.clear();
         mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
-        for (InputMethodSubtypeCompatWrapper ims: mAllEnabledSubtypesOfCurrentInputMethod) {
+        for (InputMethodSubtypeCompatWrapper ims : mAllEnabledSubtypesOfCurrentInputMethod) {
             final String locale = ims.getLocale();
             final String mode = ims.getMode();
             mLocaleSplitter.setString(locale);
@@ -172,6 +167,10 @@
                 Log.w(TAG, "Last subtype was disabled. Update to the current one.");
             }
             updateSubtype(mImm.getCurrentInputMethodSubtype());
+        } else {
+            // mLanguageBarInfo.update() will be called in updateSubtype so there is no need
+            // to call this in the if-clause above.
+            mLanguageBarInfo.update();
         }
     }
 
@@ -269,6 +268,7 @@
                 mVoiceInputWrapper.reset();
             }
         }
+        mLanguageBarInfo.update();
     }
 
     // Update the current input locale from Locale string.
@@ -303,12 +303,21 @@
     ////////////////////////////
 
     public void switchToShortcutIME() {
-        final IBinder token = mService.getWindow().getWindow().getAttributes().token;
-        if (token == null || mShortcutInputMethodInfo == null) {
+        if (mShortcutInputMethodInfo == null) {
             return;
         }
+
         final String imiId = mShortcutInputMethodInfo.getId();
         final InputMethodSubtypeCompatWrapper subtype = mShortcutSubtype;
+        switchToTargetIME(imiId, subtype);
+    }
+
+    private void switchToTargetIME(
+            final String imiId, final InputMethodSubtypeCompatWrapper subtype) {
+        final IBinder token = mService.getWindow().getWindow().getAttributes().token;
+        if (token == null) {
+            return;
+        }
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
@@ -412,11 +421,7 @@
     //////////////////////////////////
 
     public int getEnabledKeyboardLocaleCount() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return mLanguageSwitcher.getLocaleCount();
-        } else {
-            return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
-        }
+        return mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
     }
 
     public boolean useSpacebarLanguageSwitcher() {
@@ -428,74 +433,37 @@
     }
 
     public Locale getInputLocale() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return mLanguageSwitcher.getInputLocale();
-        } else {
-            return mInputLocale;
-        }
+        return mInputLocale;
     }
 
     public String getInputLocaleStr() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            String inputLanguage = null;
-            inputLanguage = mLanguageSwitcher.getInputLanguage();
-            // Should return system locale if there is no Language available.
-            if (inputLanguage == null) {
-                inputLanguage = getSystemLocale().getLanguage();
-            }
-            return inputLanguage;
-        } else {
-            return mInputLocaleStr;
-        }
+        return mInputLocaleStr;
     }
 
     public String[] getEnabledLanguages() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return mLanguageSwitcher.getEnabledLanguages();
-        } else {
-            int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
-            // Workaround for explicitly specifying the voice language
-            if (enabledLanguageCount == 1) {
-                mEnabledLanguagesOfCurrentInputMethod.add(
-                        mEnabledLanguagesOfCurrentInputMethod.get(0));
-                ++enabledLanguageCount;
-            }
-            return mEnabledLanguagesOfCurrentInputMethod.toArray(
-                    new String[enabledLanguageCount]);
+        int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
+        // Workaround for explicitly specifying the voice language
+        if (enabledLanguageCount == 1) {
+            mEnabledLanguagesOfCurrentInputMethod.add(mEnabledLanguagesOfCurrentInputMethod
+                    .get(0));
+            ++enabledLanguageCount;
         }
+        return mEnabledLanguagesOfCurrentInputMethod.toArray(new String[enabledLanguageCount]);
     }
 
     public Locale getSystemLocale() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return mLanguageSwitcher.getSystemLocale();
-        } else {
-            return mSystemLocale;
-        }
+        return mSystemLocale;
     }
 
     public boolean isSystemLanguageSameAsInputLanguage() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return getSystemLocale().getLanguage().equalsIgnoreCase(
-                    getInputLocaleStr().substring(0, 2));
-        } else {
-            return mIsSystemLanguageSameAsInputLanguage;
-        }
+        return mIsSystemLanguageSameAsInputLanguage;
     }
 
     public void onConfigurationChanged(Configuration conf) {
         final Locale systemLocale = conf.locale;
         // If system configuration was changed, update all parameters.
         if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) {
-            if (mConfigUseSpacebarLanguageSwitcher) {
-                // If the system locale changes and is different from the saved
-                // locale (mSystemLocale), then reload the input locale list from the
-                // latin ime settings (shared prefs) and reset the input locale
-                // to the first one.
-                mLanguageSwitcher.loadLocales(mPrefs);
-                mLanguageSwitcher.setSystemLocale(systemLocale);
-            } else {
-                updateAllParameters();
-            }
+            updateAllParameters();
         }
     }
 
@@ -554,7 +522,70 @@
     // Spacebar Language Switch support //
     //////////////////////////////////////
 
-    private LanguageSwitcher mLanguageSwitcher;
+    private class LanguageBarInfo {
+        private int mCurrentKeyboardSubtypeIndex;
+        private InputMethodSubtypeCompatWrapper mNextKeyboardSubtype;
+        private InputMethodSubtypeCompatWrapper mPreviousKeyboardSubtype;
+        private String mNextLanguage;
+        private String mPreviousLanguage;
+        public LanguageBarInfo() {
+            update();
+        }
+
+        private String getNextLanguage() {
+            return mNextLanguage;
+        }
+
+        private String getPreviousLanguage() {
+            return mPreviousLanguage;
+        }
+
+        public InputMethodSubtypeCompatWrapper getNextKeyboardSubtype() {
+            return mNextKeyboardSubtype;
+        }
+
+        public InputMethodSubtypeCompatWrapper getPreviousKeyboardSubtype() {
+            return mPreviousKeyboardSubtype;
+        }
+
+        public void update() {
+            if (!mConfigUseSpacebarLanguageSwitcher
+                    || mEnabledKeyboardSubtypesOfCurrentInputMethod == null
+                    || mEnabledKeyboardSubtypesOfCurrentInputMethod.size() == 0) return;
+            mCurrentKeyboardSubtypeIndex = getCurrentIndex();
+            mNextKeyboardSubtype = getNextKeyboardSubtypeInternal(mCurrentKeyboardSubtypeIndex);
+            Locale locale = new Locale(mNextKeyboardSubtype.getLocale());
+            mNextLanguage = getDisplayLanguage(locale);
+            mPreviousKeyboardSubtype = getPreviousKeyboardSubtypeInternal(
+                    mCurrentKeyboardSubtypeIndex);
+            locale = new Locale(mPreviousKeyboardSubtype.getLocale());
+            mPreviousLanguage = getDisplayLanguage(locale);
+        }
+
+        private int normalize(int index) {
+            final int N = mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
+            final int ret = index % N;
+            return ret < 0 ? ret + N : ret;
+        }
+
+        private int getCurrentIndex() {
+            final int N = mEnabledKeyboardSubtypesOfCurrentInputMethod.size();
+            for (int i = 0; i < N; ++i) {
+                if (mEnabledKeyboardSubtypesOfCurrentInputMethod.get(i).equals(mCurrentSubtype)) {
+                    return i;
+                }
+            }
+            return 0;
+        }
+
+        private InputMethodSubtypeCompatWrapper getNextKeyboardSubtypeInternal(int index) {
+            return mEnabledKeyboardSubtypesOfCurrentInputMethod.get(normalize(index + 1));
+        }
+
+        private InputMethodSubtypeCompatWrapper getPreviousKeyboardSubtypeInternal(int index) {
+            return mEnabledKeyboardSubtypesOfCurrentInputMethod.get(normalize(index - 1));
+        }
+    }
 
     public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
         if (returnsNameInThisLocale) {
@@ -579,32 +610,16 @@
         return Character.toUpperCase(s.charAt(0)) + s.substring(1);
     }
 
-    private void updateForSpacebarLanguageSwitch() {
-        // We need to update mNeedsToDisplayLanguage in onStartInputView because
-        // getEnabledKeyboardLocaleCount could have been changed.
-        mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1
-                && getSystemLocale().getLanguage().equalsIgnoreCase(
-                        getInputLocale().getLanguage()));
-    }
-
     public String getInputLanguageName() {
         return getDisplayLanguage(getInputLocale());
     }
 
     public String getNextInputLanguageName() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return getDisplayLanguage(mLanguageSwitcher.getNextInputLocale());
-        } else {
-            return "";
-        }
+        return mLanguageBarInfo.getNextLanguage();
     }
 
     public String getPreviousInputLanguageName() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            return getDisplayLanguage(mLanguageSwitcher.getPrevInputLocale());
-        } else {
-            return "";
-        }
+        return mLanguageBarInfo.getPreviousLanguage();
     }
 
     /////////////////////////////
@@ -644,31 +659,23 @@
         return voiceInputSupportedLocales.contains(locale);
     }
 
-    public void loadSettings() {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            mLanguageSwitcher.loadLocales(mPrefs);
-        }
+    private void changeToNextSubtype() {
+        final InputMethodSubtypeCompatWrapper subtype =
+                mLanguageBarInfo.getNextKeyboardSubtype();
+        switchToTargetIME(mInputMethodId, subtype);
     }
 
-    public void toggleLanguage(boolean reset, boolean next) {
-        if (mConfigUseSpacebarLanguageSwitcher) {
-            if (reset) {
-                mLanguageSwitcher.reset();
-            } else {
-                if (next) {
-                    mLanguageSwitcher.next();
-                } else {
-                    mLanguageSwitcher.prev();
-                }
-            }
-            mLanguageSwitcher.persist(mPrefs);
-        }
+    private void changeToPreviousSubtype() {
+        final InputMethodSubtypeCompatWrapper subtype =
+                mLanguageBarInfo.getPreviousKeyboardSubtype();
+        switchToTargetIME(mInputMethodId, subtype);
     }
 
-    private void initLanguageSwitcher(LatinIME service) {
-        final Configuration conf = service.getResources().getConfiguration();
-        mLanguageSwitcher = new LanguageSwitcher(service);
-        mLanguageSwitcher.loadLocales(mPrefs);
-        mLanguageSwitcher.setSystemLocale(conf.locale);
+    public void toggleLanguage(boolean next) {
+        if (next) {
+            changeToNextSubtype();
+        } else {
+            changeToPreviousSubtype();
+        }
     }
 }