Merge "Import revised translations."
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 300aaa4..4651de7 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -37,18 +37,23 @@
     <bool name="config_default_popup_preview">true</bool>
     <!-- Default values for whether quick fixes and bigram suggestions are activated -->
     <bool name="config_default_quick_fixes">true</bool>
+    <!-- Default value for bigram suggestion: while showing candidates for a word should we weigh
+         in the previous word? -->
     <bool name="config_default_bigram_suggestions">true</bool>
+    <!-- Default value for bigram prediction: after entering a word and a space only, should we look
+         at input history to suggest a hopefully helpful candidate for the next word? -->
+    <bool name="config_default_bigram_prediction">false</bool>
     <bool name="config_default_recorrection_enabled">true</bool>
     <bool name="config_default_sound_enabled">false</bool>
     <bool name="config_use_spacebar_language_switcher">true</bool>
     <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
     <bool name="config_show_mini_keyboard_at_touched_point">false</bool>
     <!-- The language is never displayed if == 0, always displayed if < 0 -->
-    <integer name="config_delay_before_fadeout_language_on_spacebar">-1</integer>
+    <integer name="config_delay_before_fadeout_language_on_spacebar">1200</integer>
     <integer name="config_duration_of_fadeout_language_on_spacebar">50</integer>
-    <integer name="config_final_fadeout_percentage_of_language_on_spacebar">15</integer>
+    <integer name="config_final_fadeout_percentage_of_language_on_spacebar">50</integer>
     <integer name="config_delay_before_preview">0</integer>
-    <integer name="config_delay_after_preview">10</integer>
+    <integer name="config_delay_after_preview">60</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/keycodes.xml b/java/res/values/keycodes.xml
index d6f9bfc..0f0a74a 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -26,8 +26,8 @@
     <integer name="key_shift">-1</integer>
     <integer name="key_switch_alpha_symbol">-2</integer>
     <integer name="key_delete">-5</integer>
-    <integer name="key_settings">-100</integer>
-    <integer name="key_voice">-102</integer>
+    <integer name="key_settings">-6</integer>
+    <integer name="key_shortcut">-8</integer>
 
     <!-- Array used for mapping key codes to description strings. -->
     <array name="key_descriptions">
@@ -45,7 +45,7 @@
         <item>@string/description_delete_key</item>
         <item>@integer/key_settings</item>
         <item>@string/description_settings_key</item>
-        <item>@integer/key_voice</item>
+        <item>@integer/key_shortcut</item>
         <item>@string/description_voice_key</item>
     </array>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 66dc7d7..dca4b91 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -38,7 +38,13 @@
     <string name="general_category">General</string>
 
     <!-- Category title for text prediction -->
-    <string name="prediction_category">Text correction</string>
+    <string name="correction_category">Text correction</string>
+
+    <!-- Category title for ngrams  -->
+    <string name="ngram_category">Suggestions based on previous words</string>
+
+    <!-- Category title for misc options  -->
+    <string name="misc_category">Other options</string>
 
     <!-- Option to enable auto capitalization of sentences -->
     <string name="auto_cap">Auto-capitalization</string>
@@ -55,6 +61,8 @@
     <string name="prefs_suggestion_visibility_show_name">Always show</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name">Show on portrait mode</string>
     <string name="prefs_suggestion_visibility_hide_name">Always hide</string>
+    <!-- Option to enable spacebar language switcher [CHAR LIMIT=20]-->
+    <string name="prefs_use_spacebar_language_switch">Use the spacebar language switcher</string>
 
     <!-- Option to show/hide the settings key -->
     <string name="prefs_settings_key">Show settings key</string>
@@ -78,9 +86,13 @@
     <string name="auto_correction_threshold_mode_aggeressive">Aggressive</string>
 
     <!-- Option to enable bigram correction -->
-    <string name="bigram_suggestion">Bigram Suggestions</string>
+    <string name="bigram_suggestion">Bigram suggestions</string>
     <!-- Description for auto correction -->
     <string name="bigram_suggestion_summary">Use previous word to improve suggestion</string>
+    <!-- Option to enable using user-history bigram when no input -->
+    <string name="bigram_prediction">Bigram prediction</string>
+    <!-- Description for auto correction -->
+    <string name="bigram_prediction_summary">Use previous word also for prediction</string>
 
     <!-- Indicates that a word has been added to the dictionary -->
     <string name="added_word"><xliff:g id="word">%s</xliff:g> : Saved</string>
diff --git a/java/res/xml-xlarge/kbd_key_styles.xml b/java/res/xml-xlarge/kbd_key_styles.xml
index 4fefe00..57eaccb 100644
--- a/java/res/xml-xlarge/kbd_key_styles.xml
+++ b/java/res/xml-xlarge/kbd_key_styles.xml
@@ -73,7 +73,7 @@
                 latin:parentStyle="functionalKeyStyle" />
             <key-style
                 latin:styleName="micKeyStyle"
-                latin:code="@integer/key_voice"
+                latin:code="@integer/key_shortcut"
                 latin:keyIcon="@drawable/sym_keyboard_voice_holo"
                 latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
                 latin:parentStyle="functionalKeyStyle" />
@@ -127,7 +127,7 @@
                 latin:parentStyle="functionalKeyStyle" />
             <key-style
                 latin:styleName="micKeyStyle"
-                latin:code="@integer/key_voice"
+                latin:code="@integer/key_shortcut"
                 latin:keyIcon="@drawable/sym_bkeyboard_mic"
                 latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
                 latin:parentStyle="functionalKeyStyle" />
diff --git a/java/res/xml/kbd_ar_rows.xml b/java/res/xml/kbd_ar_rows.xml
index d5264fd..b2ea457 100644
--- a/java/res/xml/kbd_ar_rows.xml
+++ b/java/res/xml/kbd_ar_rows.xml
@@ -87,7 +87,7 @@
             latin:keyEdgeFlags="right" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.7%p"
     >
         <Key
             latin:keyLabel="ظ"
@@ -112,7 +112,7 @@
             latin:keyLabel="ث" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:visualInsetsLeft="2%p"
+            latin:keyWidth="12%p"
             latin:keyEdgeFlags="right" />
     </Row>
     <include latin:keyboardLayout="@xml/kbd_qwerty_row4" />
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index bfc7c4f..4792e1f 100644
--- a/java/res/xml/kbd_key_styles.xml
+++ b/java/res/xml/kbd_key_styles.xml
@@ -118,7 +118,7 @@
                 latin:parentStyle="functionalKeyStyle" />
             <key-style
                 latin:styleName="micKeyStyle"
-                latin:code="@integer/key_voice"
+                latin:code="@integer/key_shortcut"
                 latin:keyIcon="@drawable/sym_keyboard_mic"
                 latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
                 latin:parentStyle="settingsPopupStyle" />
@@ -191,7 +191,7 @@
                 latin:parentStyle="functionalKeyStyle" />
             <key-style
                 latin:styleName="micKeyStyle"
-                latin:code="@integer/key_voice"
+                latin:code="@integer/key_shortcut"
                 latin:keyIcon="@drawable/sym_bkeyboard_mic"
                 latin:iconPreview="@drawable/sym_keyboard_feedback_mic"
                 latin:parentStyle="settingsPopupStyle" />
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index cbfc3c3..24a1d45 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -66,8 +66,8 @@
             android:summary="@string/language_selection_summary" />
     </PreferenceCategory>
     <PreferenceCategory
-        android:title="@string/prediction_category"
-        android:key="prediction_settings">
+        android:title="@string/correction_category"
+        android:key="correction_settings">
         <CheckBoxPreference
             android:key="quick_fixes"
             android:title="@string/quick_fixes"
@@ -90,31 +90,46 @@
             android:entryValues="@array/prefs_suggestion_visibility_values"
             android:entries="@array/prefs_suggestion_visibilities"
             android:defaultValue="@string/prefs_suggestion_visibility_default_value" />
+    </PreferenceCategory>
+    <PreferenceCategory
+        android:title="@string/ngram_category"
+        android:key="ngram_settings">
         <CheckBoxPreference
             android:key="bigram_suggestion"
             android:title="@string/bigram_suggestion"
             android:summary="@string/bigram_suggestion_summary"
             android:persistent="true"
             android:defaultValue="true" />
+        <CheckBoxPreference
+            android:key="bigram_prediction"
+            android:dependency="bigram_suggestion"
+            android:title="@string/bigram_prediction"
+            android:summary="@string/bigram_prediction_summary"
+            android:persistent="true"
+            android:defaultValue="false" />
     </PreferenceCategory>
-    <CheckBoxPreference
-        android:key="usability_study_mode"
-        android:title="@string/prefs_usability_study_mode"
-        android:persistent="true"
-        android:defaultValue="false" />
-    <CheckBoxPreference
-        android:key="enable_logging"
-        android:title="@string/prefs_enable_log"
-        android:summary="@string/prefs_description_log"
-        android:persistent="true"
-        android:defaultValue="true" />
-    <ListPreference
-        android:key="pref_keyboard_layout_20100902"
-        android:title="@string/keyboard_layout"
-        android:persistent="true"
-        android:entryValues="@array/keyboard_layout_modes_values"
-        android:entries="@array/keyboard_layout_modes"
-        android:defaultValue="@string/config_default_keyboard_theme_id" />
+    <PreferenceCategory
+        android:title="@string/misc_category"
+        android:key="misc_settings">
+      <CheckBoxPreference
+          android:key="usability_study_mode"
+          android:title="@string/prefs_usability_study_mode"
+          android:persistent="true"
+          android:defaultValue="false" />
+      <CheckBoxPreference
+          android:key="enable_logging"
+          android:title="@string/prefs_enable_log"
+          android:summary="@string/prefs_description_log"
+          android:persistent="true"
+          android:defaultValue="true" />
+      <ListPreference
+          android:key="pref_keyboard_layout_20100902"
+          android:title="@string/keyboard_layout"
+          android:persistent="true"
+          android:entryValues="@array/keyboard_layout_modes_values"
+          android:entries="@array/keyboard_layout_modes"
+          android:defaultValue="@string/config_default_keyboard_theme_id" />
+    </PreferenceCategory>
     <!-- <Preference
         android:title="Debug Settings"
         android:key="debug_settings">
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 2dad171..477461d 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -36,6 +36,13 @@
             />
 
     <CheckBoxPreference
+            android:key="use_spacebar_language_switch"
+            android:title="@string/prefs_use_spacebar_language_switch"
+            android:persistent="true"
+            android:defaultValue="false"
+            />
+
+    <CheckBoxPreference
             android:key="debug_mode"
             android:title="@string/prefs_debug_mode"
             android:persistent="true"
diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
index e4b2e03..1a606ea 100644
--- a/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
+++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/LanguageSwitcher.java
@@ -83,6 +83,7 @@
         String currentLanguage   = sp.getString(Settings.PREF_INPUT_LANGUAGE, null);
         if (TextUtils.isEmpty(selectedLanguages)) {
             mSelectedLanguageArray = EMPTY_STIRNG_ARRAY;
+            mSelectedLanguages = null;
             loadDefaults();
             if (mLocales.size() == 0) {
                 return false;
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index fdfe282..c02c224 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -68,19 +68,14 @@
     public static final int CODE_DUMMY = 0;
     public static final int CODE_SHIFT = -1;
     public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
-    public static final int CODE_CANCEL = -3;
-    public static final int CODE_DONE = -4;
+    public static final int CODE_CAPSLOCK = -3;
+    public static final int CODE_CANCEL = -4;
     public static final int CODE_DELETE = -5;
-    public static final int CODE_ALT = -6;
+    public static final int CODE_SETTINGS = -6;
+    public static final int CODE_SETTINGS_LONGPRESS = -7;
+    public static final int CODE_SHORTCUT = -8;
     // Code value representing the code is not specified.
     public static final int CODE_UNSPECIFIED = -99;
-    public static final int CODE_SETTINGS = -100;
-    public static final int CODE_SETTINGS_LONGPRESS = -101;
-    // TODO: remove this once LatinIME stops referring to this.
-    public static final int CODE_VOICE = -102;
-    public static final int CODE_CAPSLOCK = -103;
-    public static final int CODE_NEXT_LANGUAGE = -104;
-    public static final int CODE_PREV_LANGUAGE = -105;
 
     /** Horizontal gap default for all rows */
     private int mDefaultHorizontalGap;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index a58ad23..e163457 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -146,11 +146,6 @@
         // Update the settings key state because number of enabled IMEs could have been changed
         mSettingsKeyEnabledInSettings = getSettingsKeyMode(mPrefs, mInputMethodService);
         final KeyboardId id = getKeyboardId(attribute, isSymbols);
-
-        final Keyboard oldKeyboard = mInputView.getKeyboard();
-        if (oldKeyboard != null && oldKeyboard.mId.equals(id))
-            return;
-
         makeSymbolsKeyboardIds(id.mMode, attribute);
         mCurrentId = id;
         mInputView.setKeyPreviewEnabled(mInputMethodService.getPopupOn());
@@ -195,6 +190,7 @@
         // we should reset the text fade factor. It is also applicable to shortcut key.
         keyboard.setSpacebarTextFadeFactor(0.0f, null);
         keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady(), null);
+        keyboard.setSpacebarSlidingLanguageSwitchDiff(0);
         return keyboard;
     }
 
@@ -296,12 +292,6 @@
         return null;
     }
 
-    public void keyReleased() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null)
-            latinKeyboard.keyReleased();
-    }
-
     public boolean isShiftedOrShiftLocked() {
         LatinKeyboard latinKeyboard = getLatinKeyboard();
         if (latinKeyboard != null)
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 95ecb3b..c368952 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -139,6 +139,7 @@
 
     private final boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
+    private int mOldKeyIndex;
 
     // Accessibility
     private boolean mIsAccessibilityEnabled;
@@ -202,7 +203,7 @@
                     break;
                 case MSG_REPEAT_KEY: {
                     final PointerTracker tracker = (PointerTracker)msg.obj;
-                    tracker.repeatKey(msg.arg1);
+                    tracker.onRepeatKey(msg.arg1);
                     startKeyRepeatTimer(mKeyRepeatInterval, msg.arg1, tracker);
                     break;
                 }
@@ -1270,10 +1271,6 @@
         }
 
         if (mHandler.isInKeyRepeat()) {
-            // It will keep being in the key repeating mode while the key is being pressed.
-            if (action == MotionEvent.ACTION_MOVE) {
-                return true;
-            }
             final PointerTracker tracker = getPointerTracker(id);
             // Key repeating timer will be canceled if 2 or more keys are in action, and current
             // event (UP or DOWN) is non-modifier key.
@@ -1291,12 +1288,21 @@
             PointerTracker tracker = getPointerTracker(0);
             if (pointerCount == 1 && oldPointerCount == 2) {
                 // Multi-touch to single touch transition.
-                // Send a down event for the latest pointer.
-                tracker.onDownEvent(x, y, eventTime, null);
+                // Send a down event for the latest pointer if the key is different from the
+                // previous key.
+                final int newKeyIndex = tracker.getKeyIndexOn(x, y);
+                if (mOldKeyIndex != newKeyIndex) {
+                    tracker.onDownEvent(x, y, eventTime, null);
+                    if (action == MotionEvent.ACTION_UP)
+                        tracker.onUpEvent(x, y, eventTime, null);
+                }
             } else if (pointerCount == 2 && oldPointerCount == 1) {
                 // Single-touch to multi-touch transition.
                 // Send an up event for the last pointer.
-                tracker.onUpEvent(tracker.getLastX(), tracker.getLastY(), eventTime, null);
+                final int lastX = tracker.getLastX();
+                final int lastY = tracker.getLastY();
+                mOldKeyIndex = tracker.getKeyIndexOn(lastX, lastY);
+                tracker.onUpEvent(lastX, lastY, eventTime, null);
             } else if (pointerCount == 1 && oldPointerCount == 1) {
                 tracker.onTouchEvent(action, x, y, eventTime, null);
             } else {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 3a5dbd2..a1d0607 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -46,6 +46,9 @@
 public class LatinKeyboard extends Keyboard {
     private static final int SPACE_LED_LENGTH_PERCENT = 80;
 
+    public static final int CODE_NEXT_LANGUAGE = -100;
+    public static final int CODE_PREV_LANGUAGE = -101;
+
     private final Context mContext;
     private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
 
@@ -53,17 +56,15 @@
     private final Key mSpaceKey;
     private final Drawable mSpaceIcon;
     private final Drawable mSpacePreviewIcon;
-    private final int[] mSpaceKeyIndexArray;
+    private final int mSpaceKeyIndex;
     private final Drawable mSpaceAutoCorrectionIndicator;
     private final Drawable mButtonArrowLeftIcon;
     private final Drawable mButtonArrowRightIcon;
     private final int mSpacebarTextColor;
     private final int mSpacebarTextShadowColor;
-    private final int mSpacebarVerticalCorrection;
     private float mSpacebarTextFadeFactor = 0.0f;
-    private int mSpaceDragStartX;
-    private int mSpaceDragLastDiff;
-    private boolean mCurrentlyInSpace;
+    private final int mSpacebarLanguageSwitchThreshold;
+    private int mSpacebarSlidingLanguageSwitchDiff;
     private SlidingLocaleDrawable mSlidingLocaleIcon;
     private final HashMap<Integer, SoftReference<BitmapDrawable>> mSpaceDrawableCache =
             new HashMap<Integer, SoftReference<BitmapDrawable>>();
@@ -102,7 +103,7 @@
             case CODE_SPACE:
                 spaceKeyIndex = index;
                 break;
-            case CODE_VOICE:
+            case CODE_SHORTCUT:
                 shortcutKeyIndex = index;
                 break;
             }
@@ -112,7 +113,7 @@
         mSpaceKey = (spaceKeyIndex >= 0) ? keys.get(spaceKeyIndex) : null;
         mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null;
         mSpacePreviewIcon = (mSpaceKey != null) ? mSpaceKey.getPreviewIcon() : null;
-        mSpaceKeyIndexArray = new int[] { spaceKeyIndex };
+        mSpaceKeyIndex = spaceKeyIndex;
 
         mShortcutKey = (shortcutKeyIndex >= 0) ? keys.get(shortcutKeyIndex) : null;
         mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null;
@@ -130,8 +131,8 @@
         mSpaceAutoCorrectionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led);
         mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left);
         mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right);
-        mSpacebarVerticalCorrection = res.getDimensionPixelOffset(
-                R.dimen.spacebar_vertical_correction);
+        // The threshold is "key width" x 1.25
+        mSpacebarLanguageSwitchThreshold = (getMostCommonKeyWidth() * 5) / 4;
     }
 
     public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) {
@@ -322,7 +323,14 @@
         return buffer;
     }
 
-    private void updateLocaleDrag(int diff) {
+    public void setSpacebarSlidingLanguageSwitchDiff(int diff) {
+        mSpacebarSlidingLanguageSwitchDiff = diff;
+    }
+
+    public void updateSpacebarPreviewIcon(int diff) {
+        if (mSpacebarSlidingLanguageSwitchDiff == diff)
+            return;
+        mSpacebarSlidingLanguageSwitchDiff = diff;
         if (mSlidingLocaleIcon == null) {
             final int width = Math.max(mSpaceKey.mWidth,
                     (int)(getMinWidth() * SPACEBAR_POPUP_MIN_RATIO));
@@ -330,7 +338,6 @@
             mSlidingLocaleIcon =
                     new SlidingLocaleDrawable(mContext, mSpacePreviewIcon, width, height);
             mSlidingLocaleIcon.setBounds(0, 0, width, height);
-            mSpaceKey.setPreviewIcon(mSlidingLocaleIcon);
         }
         mSlidingLocaleIcon.setDiff(diff);
         if (Math.abs(diff) == Integer.MAX_VALUE) {
@@ -341,85 +348,45 @@
         mSpaceKey.getPreviewIcon().invalidateSelf();
     }
 
-    // This method is called when "popup on keypress" is off.
+    public boolean shouldTriggerSpacebarSlidingLanguageSwitch(int diff) {
+        return Math.abs(diff) > mSpacebarLanguageSwitchThreshold;
+    }
+
+    /**
+     * Return true if spacebar needs showing preview even when "popup on keypress" is off.
+     * @param keyIndex index of the pressing key
+     * @return true if spacebar needs showing preview
+     */
     @Override
     public boolean needSpacebarPreview(int keyIndex) {
+        // This method is called when "popup on keypress" is off.
         if (!mSubtypeSwitcher.useSpacebarLanguageSwitcher())
             return false;
         // Dismiss key preview.
         if (keyIndex == KeyDetector.NOT_A_KEY)
             return true;
         // Key is not a spacebar.
-        if (keyIndex != mSpaceKeyIndexArray[0])
+        if (keyIndex != mSpaceKeyIndex)
             return false;
         // The language switcher will be displayed only when the dragging distance is greater
-        // than average key width of this keyboard.
-        return Math.abs(mSpaceDragLastDiff) > getMostCommonKeyWidth();
+        // than the threshold.
+        return shouldTriggerSpacebarSlidingLanguageSwitch(mSpacebarSlidingLanguageSwitchDiff);
     }
 
     public int getLanguageChangeDirection() {
-        if (mSpaceKey == null || SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() <= 1
-                || Math.abs(mSpaceDragLastDiff) < getMostCommonKeyWidth() * SPACEBAR_DRAG_WIDTH) {
+        if (mSpaceKey == null || mSubtypeSwitcher.getEnabledKeyboardLocaleCount() <= 1
+                || Math.abs(mSpacebarSlidingLanguageSwitchDiff)
+                    < getMostCommonKeyWidth() * SPACEBAR_DRAG_WIDTH) {
             return 0; // No change
         }
-        return mSpaceDragLastDiff > 0 ? 1 : -1;
-    }
-
-    public void keyReleased() {
-        mCurrentlyInSpace = false;
-        mSpaceDragLastDiff = 0;
-        if (mSpaceKey != null) {
-            updateLocaleDrag(Integer.MAX_VALUE);
-        }
-    }
-
-    /**
-     * Does the magic of locking the touch gesture into the spacebar when
-     * switching input languages.
-     */
-    @Override
-    public boolean isInside(Key key, int pointX, int pointY) {
-        int x = pointX;
-        int y = pointY;
-        final int code = key.mCode;
-        if (code == CODE_SPACE) {
-            y += mSpacebarVerticalCorrection;
-            if (SubtypeSwitcher.getInstance().useSpacebarLanguageSwitcher()
-                    && SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() > 1) {
-                if (mCurrentlyInSpace) {
-                    int diff = x - mSpaceDragStartX;
-                    if (Math.abs(diff - mSpaceDragLastDiff) > 0) {
-                        updateLocaleDrag(diff);
-                    }
-                    mSpaceDragLastDiff = diff;
-                    return true;
-                } else {
-                    boolean isOnSpace = key.isOnKey(x, y);
-                    if (isOnSpace) {
-                        mCurrentlyInSpace = true;
-                        mSpaceDragStartX = x;
-                        updateLocaleDrag(0);
-                    }
-                    return isOnSpace;
-                }
-            }
-        }
-
-        // Lock into the spacebar
-        if (mCurrentlyInSpace) return false;
-
-        return key.isOnKey(x, y);
+        return mSpacebarSlidingLanguageSwitchDiff > 0 ? 1 : -1;
     }
 
     @Override
     public int[] getNearestKeys(int x, int y) {
-        if (mCurrentlyInSpace) {
-            return mSpaceKeyIndexArray;
-        } else {
-            // Avoid dead pixels at edges of the keyboard
-            return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
-                    Math.max(0, Math.min(y, getHeight() - 1)));
-        }
+        // Avoid dead pixels at edges of the keyboard
+        return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
+                Math.max(0, Math.min(y, getHeight() - 1)));
     }
 
     private static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 9120745..efc446a 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -68,11 +68,6 @@
 
     @Override
     public void setKeyboard(Keyboard newKeyboard) {
-        final LatinKeyboard oldKeyboard = getLatinKeyboard();
-        if (oldKeyboard != null) {
-            // Reset old keyboard state before switching to new keyboard.
-            oldKeyboard.keyReleased();
-        }
         super.setKeyboard(newKeyboard);
         // One-seventh of the keyboard width seems like a reasonable threshold
         mJumpThresholdSquare = newKeyboard.getMinWidth() / 7;
@@ -216,8 +211,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent me) {
-        LatinKeyboard keyboard = getLatinKeyboard();
-        if (keyboard == null) return true;
+        if (getLatinKeyboard() == null) return true;
 
         // If there was a sudden jump, return without processing the actual motion event.
         if (handleSuddenJump(me)) {
@@ -226,24 +220,6 @@
             return true;
         }
 
-        // Reset any bounding box controls in the keyboard
-        if (me.getAction() == MotionEvent.ACTION_DOWN) {
-            keyboard.keyReleased();
-        }
-
-        if (me.getAction() == MotionEvent.ACTION_UP) {
-            int languageDirection = keyboard.getLanguageChangeDirection();
-            if (languageDirection != 0) {
-                getOnKeyboardActionListener().onCodeInput(
-                        languageDirection == 1
-                        ? Keyboard.CODE_NEXT_LANGUAGE : Keyboard.CODE_PREV_LANGUAGE,
-                        null, mLastX, mLastY);
-                me.setAction(MotionEvent.ACTION_CANCEL);
-                keyboard.keyReleased();
-                return super.onTouchEvent(me);
-            }
-        }
-
         return super.onTouchEvent(me);
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 1b1aa49..e3161f6 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -19,6 +19,7 @@
 import com.android.inputmethod.keyboard.KeyboardView.UIHandler;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
 
 import android.content.res.Resources;
 import android.util.Log;
@@ -90,6 +91,12 @@
     // ignore modifier key if true
     private boolean mIgnoreModifierKey;
 
+    // TODO: Remove these hacking variables
+    // true if this pointer is in sliding language switch
+    private boolean mIsInSlidingLanguageSwitch;
+    private int mSpaceKeyIndex;
+    private final SubtypeSwitcher mSubtypeSwitcher;
+
     // Empty {@link KeyboardActionListener}
     private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() {
         @Override
@@ -129,6 +136,7 @@
                 R.dimen.config_touch_noise_threshold_distance);
         mTouchNoiseThresholdDistanceSquared = (int)(
                 touchNoiseThresholdDistance * touchNoiseThresholdDistance);
+        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
     }
 
     public void setOnKeyboardActionListener(KeyboardActionListener listener) {
@@ -243,6 +251,10 @@
         return key != null && key.mCode == Keyboard.CODE_SHIFT;
     }
 
+    public int getKeyIndexOn(int x, int y) {
+        return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+    }
+
     public boolean isSpaceKey(int keyIndex) {
         Key key = getKey(keyIndex);
         return key != null && key.mCode == Keyboard.CODE_SPACE;
@@ -338,6 +350,7 @@
         mKeyAlreadyProcessed = false;
         mIsRepeatableKey = false;
         mIsInSlidingKeyInput = false;
+        mIsInSlidingLanguageSwitch = false;
         mIgnoreModifierKey = false;
         if (isValidKeyIndex(keyIndex)) {
             // This onPress call may have changed keyboard layout. Those cases are detected at
@@ -346,14 +359,7 @@
             if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), false))
                 keyIndex = mKeyState.onDownKey(x, y, eventTime);
 
-            // Accessibility disables key repeat because users may need to pause on a key to hear
-            // its spoken description.
-            final Key key = getKey(keyIndex);
-            if (key != null && key.mRepeatable && !mIsAccessibilityEnabled) {
-                repeatKey(keyIndex);
-                mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
-                mIsRepeatableKey = true;
-            }
+            startRepeatKey(keyIndex);
             startLongPressTimer(keyIndex);
             showKeyPreview(keyIndex);
             setPressedKeyGraphics(keyIndex);
@@ -374,6 +380,12 @@
             return;
         final PointerTrackerKeyState keyState = mKeyState;
 
+        // TODO: Remove this hacking code
+        if (mIsInSlidingLanguageSwitch) {
+            ((LatinKeyboard)mKeyboard).updateSpacebarPreviewIcon(x - keyState.getKeyX());
+            showKeyPreview(mSpaceKeyIndex);
+            return;
+        }
         final int lastX = keyState.getLastX();
         final int lastY = keyState.getLastY();
         final int oldKeyIndex = keyState.getKeyIndex();
@@ -399,7 +411,8 @@
                 setReleasedKeyGraphics(oldKeyIndex);
                 callListenerOnRelease(oldKey, oldKey.mCode, true);
                 startSlidingKeyInput(oldKey);
-                mHandler.cancelLongPressTimers();
+                mHandler.cancelKeyTimers();
+                startRepeatKey(keyIndex);
                 if (mIsAllowedSlidingKeyInput) {
                     // This onPress call may have changed keyboard layout. Those cases are detected
                     // at {@link #setKeyboard}. In those cases, we should update keyIndex according
@@ -428,11 +441,26 @@
                         dismissKeyPreview();
                         setReleasedKeyGraphics(oldKeyIndex);
                     }
-                    return;
                 }
-            } else if (mKeyboard.needSpacebarPreview(keyIndex)) {
-                // Display spacebar slide language switcher.
-                showKeyPreview(keyIndex);
+            }
+            // TODO: Remove this hack code
+            else if (isSpaceKey(keyIndex) && !mIsInSlidingLanguageSwitch
+                    && mKeyboard instanceof LatinKeyboard) {
+                final LatinKeyboard keyboard = ((LatinKeyboard)mKeyboard);
+                if (mSubtypeSwitcher.useSpacebarLanguageSwitcher()
+                        && mSubtypeSwitcher.getEnabledKeyboardLocaleCount() > 1) {
+                    final int diff = x - keyState.getKeyX();
+                    if (keyboard.shouldTriggerSpacebarSlidingLanguageSwitch(diff)) {
+                        // Detect start sliding language switch.
+                        mIsInSlidingLanguageSwitch = true;
+                        mSpaceKeyIndex = keyIndex;
+                        keyboard.updateSpacebarPreviewIcon(diff);
+                        // Display spacebar slide language switcher.
+                        showKeyPreview(keyIndex);
+                        if (queue != null)
+                            queue.releaseAllPointersExcept(this, eventTime, true);
+                    }
+                }
             }
         } else {
             if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, keyIndex)) {
@@ -447,7 +475,6 @@
                 } else {
                     mKeyAlreadyProcessed = true;
                     dismissKeyPreview();
-                    return;
                 }
             }
         }
@@ -462,7 +489,7 @@
             if (isModifier()) {
                 // Before processing an up event of modifier key, all pointers already being
                 // tracked should be released.
-                queue.releaseAllPointersExcept(this, eventTime);
+                queue.releaseAllPointersExcept(this, eventTime, true);
             } else {
                 queue.releaseAllPointersOlderThan(this, eventTime);
             }
@@ -474,8 +501,10 @@
     // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
     // This pointer tracker needs to keep the key top graphics "pressed", but needs to get a
     // "virtual" up event.
-    public void onPhantomUpEvent(int x, int y, long eventTime) {
-        onUpEventInternal(x, y, eventTime, false);
+    public void onPhantomUpEvent(int x, int y, long eventTime, boolean updateReleasedKeyGraphics) {
+        if (DEBUG_EVENT)
+            printTouchEvent("onPhntEvent:", x, y, eventTime);
+        onUpEventInternal(x, y, eventTime, updateReleasedKeyGraphics);
         mKeyAlreadyProcessed = true;
     }
 
@@ -500,6 +529,20 @@
             setReleasedKeyGraphics(keyIndex);
         if (mKeyAlreadyProcessed)
             return;
+        // TODO: Remove this hacking code
+        if (mIsInSlidingLanguageSwitch) {
+            setReleasedKeyGraphics(mSpaceKeyIndex);
+            final int languageDir = ((LatinKeyboard)mKeyboard).getLanguageChangeDirection();
+            if (languageDir != 0) {
+                final int code = (languageDir == 1)
+                        ? LatinKeyboard.CODE_NEXT_LANGUAGE : LatinKeyboard.CODE_PREV_LANGUAGE;
+                // This will change keyboard layout.
+                mListener.onCodeInput(code, new int[] {code}, keyX, keyY);
+            }
+            mIsInSlidingLanguageSwitch = false;
+            ((LatinKeyboard)mKeyboard).setSpacebarSlidingLanguageSwitchDiff(0);
+            return;
+        }
         if (!mIsRepeatableKey) {
             detectAndSendKey(keyIndex, keyX, keyY);
         }
@@ -507,7 +550,8 @@
 
     public void onLongPressed(PointerTrackerQueue queue) {
         mKeyAlreadyProcessed = true;
-        queue.remove(this);
+        if (queue != null)
+            queue.remove(this);
     }
 
     public void onCancelEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
@@ -515,8 +559,10 @@
         if (DEBUG_EVENT)
             printTouchEvent("onCancelEvt:", x, y, eventTime);
 
-        if (queue != null)
+        if (queue != null) {
+            queue.releaseAllPointersExcept(this, eventTime, true);
             queue.remove(this);
+        }
         onCancelEventInternal();
     }
 
@@ -528,7 +574,21 @@
         mIsInSlidingKeyInput = false;
     }
 
-    public void repeatKey(int keyIndex) {
+    private void startRepeatKey(int keyIndex) {
+        // Accessibility disables key repeat because users may need to pause on a key to hear
+        // its spoken description.
+        final Key key = getKey(keyIndex);
+        if (key != null && key.mRepeatable && !mIsAccessibilityEnabled) {
+            dismissKeyPreview();
+            onRepeatKey(keyIndex);
+            mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
+            mIsRepeatableKey = true;
+        } else {
+            mIsRepeatableKey = false;
+        }
+    }
+
+    public void onRepeatKey(int keyIndex) {
         Key key = getKey(keyIndex);
         if (key != null) {
             detectAndSendKey(keyIndex, key.mX, key.mY);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
index 0a94100..68de8df 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerQueue.java
@@ -35,21 +35,22 @@
             if (t.isModifier()) {
                 oldestPos++;
             } else {
-                t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
+                t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime, true);
                 queue.remove(oldestPos);
             }
         }
     }
 
     public void releaseAllPointers(long eventTime) {
-        releaseAllPointersExcept(null, eventTime);
+        releaseAllPointersExcept(null, eventTime, true);
     }
 
-    public void releaseAllPointersExcept(PointerTracker tracker, long eventTime) {
+    public void releaseAllPointersExcept(PointerTracker tracker, long eventTime,
+            boolean updateReleasedKeyGraphics) {
         for (PointerTracker t : mQueue) {
             if (t == tracker)
                 continue;
-            t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime);
+            t.onPhantomUpEvent(t.getLastX(), t.getLastY(), eventTime, updateReleasedKeyGraphics);
         }
         mQueue.clear();
         if (tracker != null)
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 58e9099..7e63aac 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -197,6 +197,11 @@
         Arrays.fill(mBigramScores, 0);
 
         int codesSize = codes.size();
+        if (codesSize <= 0) {
+            // Do not return bigrams from BinaryDictionary when nothing was typed.
+            // Only use user-history bigrams (or whatever other bigram dictionaries decide).
+            return;
+        }
         Arrays.fill(mInputCodes, -1);
         int[] alternatives = codes.getCodesAt(0);
         System.arraycopy(alternatives, 0, mInputCodes, 0,
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 2f1e7c2..fd62d61 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -33,6 +33,7 @@
 
     private boolean mServiceNeedsRestart = false;
     private CheckBoxPreference mDebugMode;
+    private CheckBoxPreference mUseSpacebarLanguageSwitch;
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -60,6 +61,13 @@
                 updateDebugMode();
                 mServiceNeedsRestart = true;
             }
+        } else if (key.equals(SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCH_KEY)) {
+            if (mUseSpacebarLanguageSwitch != null) {
+                mUseSpacebarLanguageSwitch.setChecked(
+                        prefs.getBoolean(SubtypeSwitcher.USE_SPACEBAR_LANGUAGE_SWITCH_KEY,
+                                getResources().getBoolean(
+                                        R.bool.config_use_spacebar_language_switcher)));
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
index 80830c0..ea281f5 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -161,23 +161,62 @@
 
     private static final Pattern spaceRegex = Pattern.compile("\\s+");
 
+
     public static CharSequence getPreviousWord(InputConnection connection,
             String sentenceSeperators) {
         //TODO: Should fix this. This could be slow!
         CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
-        if (prev == null) {
-            return null;
-        }
+        return getPreviousWord(prev, sentenceSeperators);
+    }
+
+    // Get the word before the whitespace preceding the non-whitespace preceding the cursor.
+    // Also, it won't return words that end in a separator.
+    // Example :
+    // "abc def|" -> abc
+    // "abc def |" -> abc
+    // "abc def. |" -> abc
+    // "abc def . |" -> def
+    // "abc|" -> null
+    // "abc |" -> null
+    // "abc. def|" -> null
+    public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) {
+        if (prev == null) return null;
         String[] w = spaceRegex.split(prev);
-        if (w.length >= 2 && w[w.length-2].length() > 0) {
-            char lastChar = w[w.length-2].charAt(w[w.length-2].length() -1);
-            if (sentenceSeperators.contains(String.valueOf(lastChar))) {
-                return null;
-            }
-            return w[w.length-2];
-        } else {
-            return null;
-        }
+
+        // If we can't find two words, or we found an empty word, return null.
+        if (w.length < 2 || w[w.length - 2].length() <= 0) return null;
+
+        // If ends in a separator, return null
+        char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1);
+        if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+
+        return w[w.length - 2];
+    }
+
+    public static CharSequence getThisWord(InputConnection connection, String sentenceSeperators) {
+        final CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
+        return getThisWord(prev, sentenceSeperators);
+    }
+
+    // Get the word immediately before the cursor, even if there is whitespace between it and
+    // the cursor - but not if there is punctuation.
+    // Example :
+    // "abc def|" -> def
+    // "abc def |" -> def
+    // "abc def. |" -> null
+    // "abc def . |" -> null
+    public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) {
+        if (prev == null) return null;
+        String[] w = spaceRegex.split(prev);
+
+        // No word : return null
+        if (w.length < 1 || w[w.length - 1].length() <= 0) return null;
+
+        // If ends in a separator, return null
+        char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1);
+        if (sentenceSeperators.contains(String.valueOf(lastChar))) return null;
+
+        return w[w.length - 1];
     }
 
     public static class SelectedWord {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index a680b98..28fd6aa 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -177,7 +177,10 @@
     private boolean mJustAddedAutoSpace;
     private boolean mAutoCorrectEnabled;
     private boolean mRecorrectionEnabled;
+    // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
     private boolean mBigramSuggestionEnabled;
+    // Prediction: use bigrams to predict the next word when there is no input for it yet
+    private boolean mBigramPredictionEnabled;
     private boolean mAutoCorrectOn;
     private boolean mVibrateOn;
     private boolean mSoundOn;
@@ -266,6 +269,7 @@
         private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4;
         private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
         private static final int MSG_SPACE_TYPED = 6;
+        private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
 
         @Override
         public void handleMessage(Message msg) {
@@ -281,6 +285,9 @@
             case MSG_UPDATE_SHIFT_STATE:
                 switcher.updateShiftState();
                 break;
+            case MSG_SET_BIGRAM_PREDICTIONS:
+                updateBigramPredictions();
+                break;
             case MSG_VOICE_RESULTS:
                 mVoiceProxy.handleVoiceResults(preferCapitalization()
                         || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
@@ -333,6 +340,15 @@
             removeMessages(MSG_UPDATE_SHIFT_STATE);
         }
 
+        public void postUpdateBigramPredictions() {
+            removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
+            sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS), DELAY_UPDATE_SUGGESTIONS);
+        }
+
+        public void cancelUpdateBigramPredictions() {
+            removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
+        }
+
         public void updateVoiceResults() {
             sendMessage(obtainMessage(MSG_VOICE_RESULTS));
         }
@@ -343,11 +359,14 @@
             final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
             if (inputView != null) {
                 final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
+                // The language is always displayed when the delay is negative.
+                final boolean needsToDisplayLanguage = localeChanged
+                        || mConfigDelayBeforeFadeoutLanguageOnSpacebar < 0;
                 // The language is never displayed when the delay is zero.
                 if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0)
-                    inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f
+                    inputView.setSpacebarTextFadeFactor(needsToDisplayLanguage ? 1.0f
                             : mConfigFinalFadeoutFactorOfLanguageOnSpacebar, keyboard);
-                // The language is always displayed when the delay is negative.
+                // The fadeout animation will start when the delay is positive.
                 if (localeChanged && mConfigDelayBeforeFadeoutLanguageOnSpacebar > 0) {
                     sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard),
                             mConfigDelayBeforeFadeoutLanguageOnSpacebar);
@@ -548,7 +567,7 @@
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         LatinKeyboardView inputView = switcher.getInputView();
 
-        if(DEBUG) {
+        if (DEBUG) {
             Log.d(TAG, "onStartInputView: " + inputView);
         }
         // In landscape mode, this method gets called without the input view being created.
@@ -754,7 +773,12 @@
             }
             mComposing.setLength(0);
             mHasValidSuggestions = false;
-            mHandler.postUpdateSuggestions();
+            if (isCursorTouchingWord()) {
+                mHandler.cancelUpdateBigramPredictions();
+                mHandler.postUpdateSuggestions();
+            } else {
+                setPunctuationSuggestions();
+            }
             TextEntryState.reset();
             InputConnection ic = getCurrentInputConnection();
             if (ic != null) {
@@ -784,14 +808,20 @@
                                 || TextEntryState.isRecorrecting())
                                 && (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) {
                     if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
+                        mHandler.cancelUpdateBigramPredictions();
                         mHandler.postUpdateOldSuggestions();
                     } else {
                         abortRecorrection(false);
-                        // Show the punctuation suggestions list if the current one is not
-                        // and if not showing "Touch again to save".
-                        if (mCandidateView != null && !isShowingPunctuationList()
+                        // If showing the "touch again to save" hint, do not replace it. Else,
+                        // show the bigrams if we are at the end of the text, punctuation otherwise.
+                        if (mCandidateView != null
                                 && !mCandidateView.isShowingAddToDictionaryHint()) {
-                            setPunctuationSuggestions();
+                            InputConnection ic = getCurrentInputConnection();
+                            if (null == ic || !TextUtils.isEmpty(ic.getTextAfterCursor(1, 0))) {
+                                if (!isShowingPunctuationList()) setPunctuationSuggestions();
+                            } else {
+                                mHandler.postUpdateBigramPredictions();
+                            }
                         }
                     }
                 }
@@ -1159,16 +1189,16 @@
         case Keyboard.CODE_SETTINGS_LONGPRESS:
             onSettingsKeyLongPressed();
             break;
-        case Keyboard.CODE_NEXT_LANGUAGE:
+        case LatinKeyboard.CODE_NEXT_LANGUAGE:
             toggleLanguage(true);
             break;
-        case Keyboard.CODE_PREV_LANGUAGE:
+        case LatinKeyboard.CODE_PREV_LANGUAGE:
             toggleLanguage(false);
             break;
         case Keyboard.CODE_CAPSLOCK:
             switcher.toggleCapsLock();
             break;
-        case Keyboard.CODE_VOICE:
+        case Keyboard.CODE_SHORTCUT:
             mSubtypeSwitcher.switchToShortcutIME();
             break;
         case Keyboard.CODE_TAB:
@@ -1231,7 +1261,14 @@
                 if (mComposing.length() == 0) {
                     mHasValidSuggestions = false;
                 }
-                mHandler.postUpdateSuggestions();
+                if (1 == length) {
+                    // 1 == length means we are about to erase the last character of the word,
+                    // so we can show bigrams.
+                    mHandler.postUpdateBigramPredictions();
+                } else {
+                    // length > 1, so we still have letters to deduce a suggestion from.
+                    mHandler.postUpdateSuggestions();
+                }
             } else {
                 ic.deleteSurroundingText(1, 0);
             }
@@ -1367,6 +1404,7 @@
 
         // Should dismiss the "Touch again to save" message when handling separator
         if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
+            mHandler.cancelUpdateBigramPredictions();
             mHandler.postUpdateSuggestions();
         }
 
@@ -1406,6 +1444,7 @@
         }
 
         TextEntryState.typedCharacter((char) primaryCode, true, x, y);
+
         if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER) {
             swapPunctuationAndSpace();
         } else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
@@ -1416,10 +1455,20 @@
             TextEntryState.backToAcceptedDefault(typedWord);
             if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
                 InputConnectionCompatUtils.commitCorrection(
-                        ic,  mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
+                        ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
                 if (mCandidateView != null)
                     mCandidateView.onAutoCorrectionInverted(mBestWord);
             }
+        }
+        if (Keyboard.CODE_SPACE == primaryCode) {
+            if (!isCursorTouchingWord()) {
+                mHandler.cancelUpdateSuggestions();
+                mHandler.cancelUpdateOldSuggestions();
+                mHandler.postUpdateBigramPredictions();
+            }
+        } else {
+            // Set punctuation right away. onUpdateSelection will fire but tests whether it is
+            // already displayed or not, so it's okay.
             setPunctuationSuggestions();
         }
         mKeyboardSwitcher.updateShiftState();
@@ -1654,6 +1703,11 @@
             }
             return;
         }
+        if (!mHasValidSuggestions) {
+            // If we are not composing a word, then it was a suggestion inferred from
+            // context - no user input. We should reset the word composer.
+            mWord.reset();
+        }
         mJustAccepted = true;
         pickSuggestion(suggestion);
         // Add the word to the auto dictionary if it's not a known word
@@ -1692,7 +1746,7 @@
             // TextEntryState.State.PICKED_SUGGESTION state.
             TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
                     WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
-            setPunctuationSuggestions();
+            // From there on onUpdateSelection() will fire so suggestions will be updated
         } else if (!showingAddToDictionaryHint) {
             // If we're not showing the "Touch again to save", then show corrections again.
             // In case the cursor position doesn't change, make sure we show the suggestions again.
@@ -1807,6 +1861,30 @@
         }
     }
 
+    private static final WordComposer sEmptyWordComposer = new WordComposer();
+    private void updateBigramPredictions() {
+        if (mSuggest == null || !isSuggestionsRequested())
+            return;
+
+        if (!mBigramPredictionEnabled) {
+            setPunctuationSuggestions();
+            return;
+        }
+
+        final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
+                mWordSeparators);
+        SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
+                mKeyboardSwitcher.getInputView(), sEmptyWordComposer, prevWord);
+
+        if (builder.size() > 0) {
+            // Explicitly supply an empty typed word (the no-second-arg version of
+            // showSuggestions will retrieve the word near the cursor, we don't want that here)
+            showSuggestions(builder.build(), "");
+        } else {
+            if (!isShowingPunctuationList()) setPunctuationSuggestions();
+        }
+    }
+
     private void setPunctuationSuggestions() {
         setSuggestions(mSuggestPuncList);
         setCandidatesViewShown(isCandidateStripVisible());
@@ -1907,6 +1985,7 @@
                 ic.setComposingText(mComposing, 1);
                 TextEntryState.backspace();
             }
+            mHandler.cancelUpdateBigramPredictions();
             mHandler.postUpdateSuggestions();
         } else {
             sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
@@ -1982,7 +2061,6 @@
     public void onRelease(int primaryCode, boolean withSliding) {
         KeyboardSwitcher switcher = mKeyboardSwitcher;
         // Reset any drag flags in the keyboard
-        switcher.keyReleased();
         final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
         if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
             switcher.onReleaseShift(withSliding);
@@ -2132,6 +2210,7 @@
 
         mAutoCorrectEnabled = isAutoCorrectEnabled(prefs);
         mBigramSuggestionEnabled = mAutoCorrectEnabled && isBigramSuggestionEnabled(prefs);
+        mBigramPredictionEnabled = mBigramSuggestionEnabled && isBigramPredictionEnabled(prefs);
         loadAndSetAutoCorrectionThreshold(prefs);
 
         mVoiceProxy.loadSettings(attribute, prefs);
@@ -2216,6 +2295,11 @@
                 R.bool.config_default_bigram_suggestions));
     }
 
+    private boolean isBigramPredictionEnabled(SharedPreferences sp) {
+        return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, mResources.getBoolean(
+                R.bool.config_default_bigram_prediction));
+    }
+
     private void initSuggestPuncList() {
         if (mSuggestPuncs != null || mSuggestPuncList != null)
             return;
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 6a0b7b0..42b0fad 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -62,13 +62,18 @@
     public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
     public static final String PREF_SUBTYPES = "subtype_settings";
 
-    public static final String PREF_PREDICTION_SETTINGS_KEY = "prediction_settings";
+    public static final String PREF_CORRECTION_SETTINGS_KEY = "correction_settings";
     public static final String PREF_QUICK_FIXES = "quick_fixes";
     public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
     public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
-    public static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
     public static final String PREF_DEBUG_SETTINGS = "debug_settings";
 
+    public static final String PREF_NGRAM_SETTINGS_KEY = "ngram_settings";
+    public static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
+    public static final String PREF_BIGRAM_PREDICTIONS = "bigram_prediction";
+
+    public static final String PREF_MISC_SETTINGS_KEY = "misc_settings";
+
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
 
     // Dialog ids
@@ -80,7 +85,10 @@
     private ListPreference mSettingsKeyPreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
     private ListPreference mAutoCorrectionThreshold;
+    // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
     private CheckBoxPreference mBigramSuggestion;
+    // Prediction: use bigrams to predict the next word when there is no input for it yet
+    private CheckBoxPreference mBigramPrediction;
     private Preference mDebugSettingsPreference;
     private boolean mVoiceOn;
 
@@ -96,6 +104,7 @@
                 R.string.auto_correction_threshold_mode_index_off);
         final String currentSetting = mAutoCorrectionThreshold.getValue();
         mBigramSuggestion.setEnabled(!currentSetting.equals(autoCorrectionOff));
+        mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
     }
 
     @Override
@@ -119,6 +128,7 @@
 
         mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
         mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
+        mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
         mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
         if (mDebugSettingsPreference != null) {
             final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
@@ -131,7 +141,9 @@
         final PreferenceGroup generalSettings =
                 (PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS_KEY);
         final PreferenceGroup textCorrectionGroup =
-                (PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY);
+                (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY);
+        final PreferenceGroup bigramGroup =
+                (PreferenceGroup) findPreference(PREF_NGRAM_SETTINGS_KEY);
 
         final boolean showSettingsKeyOption = getResources().getBoolean(
                 R.bool.config_enable_show_settings_key_option);
@@ -178,6 +190,7 @@
                 R.bool.config_enable_bigram_suggestions_option);
         if (!showBigramSuggestionsOption) {
             textCorrectionGroup.removePreference(findPreference(PREF_BIGRAM_SUGGESTIONS));
+            textCorrectionGroup.removePreference(findPreference(PREF_BIGRAM_PREDICTIONS));
         }
 
         final boolean showUsabilityModeStudyOption = getResources().getBoolean(
@@ -192,7 +205,7 @@
         super.onResume();
         int autoTextSize = AutoText.getSize(getListView());
         if (autoTextSize < 1) {
-            ((PreferenceGroup) findPreference(PREF_PREDICTION_SETTINGS_KEY))
+            ((PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY))
                     .removePreference(mQuickFixes);
         }
         if (!VoiceProxy.VOICE_INSTALLED
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 6321955..160b677 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -52,6 +52,8 @@
     private static final String VOICE_MODE = "voice";
     private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
             "requireNetworkConnectivity";
+    public static final String USE_SPACEBAR_LANGUAGE_SWITCH_KEY = "use_spacebar_language_switch";
+
     private final TextUtils.SimpleStringSplitter mLocaleSplitter =
             new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
 
@@ -61,6 +63,7 @@
     private /* final */ Resources mResources;
     private /* final */ ConnectivityManager mConnectivityManager;
     private /* final */ boolean mConfigUseSpacebarLanguageSwitcher;
+    private /* final */ SharedPreferences mPrefs;
     private final ArrayList<InputMethodSubtypeCompatWrapper>
             mEnabledKeyboardSubtypesOfCurrentInputMethod =
                     new ArrayList<InputMethodSubtypeCompatWrapper>();
@@ -112,10 +115,8 @@
         mInputLocaleStr = null;
         mCurrentSubtype = null;
         mAllEnabledSubtypesOfCurrentInputMethod = null;
-        // TODO: Voice input should be created here
         mVoiceInputWrapper = null;
-        mConfigUseSpacebarLanguageSwitcher = service.getResources().getBoolean(
-                R.bool.config_use_spacebar_language_switcher);
+        mPrefs = prefs;
 
         final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
         mIsNetworkConnected = (info != null && info.isConnected());
@@ -133,6 +134,9 @@
     // Update parameters which are changed outside LatinIME. This parameters affect UI so they
     // should be updated every time onStartInputview.
     public void updateParametersOnStartInputView() {
+        mConfigUseSpacebarLanguageSwitcher = mPrefs.getBoolean(USE_SPACEBAR_LANGUAGE_SWITCH_KEY,
+                mService.getResources().getBoolean(
+                        R.bool.config_use_spacebar_language_switcher));
         updateEnabledSubtypes();
         updateShortcutIME();
     }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index f372062..15743ee 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -265,6 +265,16 @@
         return sb;
     }
 
+    protected void addBigramToSuggestions(CharSequence bigram) {
+        final int poolSize = mStringPool.size();
+        final StringBuilder sb = poolSize > 0 ?
+                (StringBuilder) mStringPool.remove(poolSize - 1)
+                        : new StringBuilder(getApproxMaxWordLength());
+        sb.setLength(0);
+        sb.append(bigram);
+        mSuggestions.add(sb);
+    }
+
     // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
     public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
             CharSequence prevWordForBigram) {
@@ -286,7 +296,7 @@
         }
         mTypedWord = typedWord;
 
-        if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
+        if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
                 || mCorrectionMode == CORRECTION_BASIC)) {
             // At first character typed, search only the bigrams
             Arrays.fill(mBigramScores, 0);
@@ -300,21 +310,26 @@
                 for (final Dictionary dictionary : mBigramDictionaries.values()) {
                     dictionary.getBigrams(wordComposer, prevWordForBigram, this);
                 }
-                char currentChar = wordComposer.getTypedWord().charAt(0);
-                char currentCharUpper = Character.toUpperCase(currentChar);
-                int count = 0;
-                int bigramSuggestionSize = mBigramSuggestions.size();
-                for (int i = 0; i < bigramSuggestionSize; i++) {
-                    if (mBigramSuggestions.get(i).charAt(0) == currentChar
-                            || mBigramSuggestions.get(i).charAt(0) == currentCharUpper) {
-                        int poolSize = mStringPool.size();
-                        StringBuilder sb = poolSize > 0 ?
-                                (StringBuilder) mStringPool.remove(poolSize - 1)
-                                : new StringBuilder(getApproxMaxWordLength());
-                        sb.setLength(0);
-                        sb.append(mBigramSuggestions.get(i));
-                        mSuggestions.add(count++, sb);
-                        if (count > mPrefMaxSuggestions) break;
+                if (TextUtils.isEmpty(typedWord)) {
+                    // Nothing entered: return all bigrams for the previous word
+                    int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
+                    for (int i = 0; i < insertCount; ++i) {
+                        addBigramToSuggestions(mBigramSuggestions.get(i));
+                    }
+                } else {
+                    // Word entered: return only bigrams that match the first char of the typed word
+                    final char currentChar = typedWord.charAt(0);
+                    final char currentCharUpper = Character.toUpperCase(currentChar);
+                    int count = 0;
+                    final int bigramSuggestionSize = mBigramSuggestions.size();
+                    for (int i = 0; i < bigramSuggestionSize; i++) {
+                        final CharSequence bigramSuggestion = mBigramSuggestions.get(i);
+                        final char bigramSuggestionFirstChar = bigramSuggestion.charAt(0);
+                        if (bigramSuggestionFirstChar == currentChar
+                                || bigramSuggestionFirstChar == currentCharUpper) {
+                            addBigramToSuggestions(bigramSuggestion);
+                            if (++count > mPrefMaxSuggestions) break;
+                        }
                     }
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
index bb6642c..a32a646 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -162,6 +162,10 @@
         if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) {
             word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
         }
+        // Do not insert a word as a bigram of itself
+        if (word1.equals(word2)) {
+            return 0;
+        }
 
         int freq = super.addBigram(word1, word2, FREQUENCY_FOR_TYPED);
         if (freq > FREQUENCY_MAX) freq = FREQUENCY_MAX;
diff --git a/tests/src/com/android/inputmethod/latin/UtilsTests.java b/tests/src/com/android/inputmethod/latin/UtilsTests.java
new file mode 100644
index 0000000..5c0b03a
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/UtilsTests.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010,2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.test.AndroidTestCase;
+
+import com.android.inputmethod.latin.tests.R;
+
+public class UtilsTests extends AndroidTestCase {
+
+    // The following is meant to be a reasonable default for
+    // the "word_separators" resource.
+    private static final String sSeparators = ".,:;!?-";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    /************************** Tests ************************/
+
+    /**
+     * Test for getting previous word (for bigram suggestions)
+     */
+    public void testGetPreviousWord() {
+        // If one of the following cases breaks, the bigram suggestions won't work.
+        assertEquals(EditingUtils.getPreviousWord("abc def", sSeparators), "abc");
+        assertNull(EditingUtils.getPreviousWord("abc", sSeparators));
+        assertNull(EditingUtils.getPreviousWord("abc. def", sSeparators));
+
+        // The following tests reflect the current behavior of the function
+        // EditingUtils#getPreviousWord.
+        // TODO: However at this time, the code does never go
+        // into such a path, so it should be safe to change the behavior of
+        // this function if needed - especially since it does not seem very
+        // logical. These tests are just there to catch any unintentional
+        // changes in the behavior of the EditingUtils#getPreviousWord method.
+        assertEquals(EditingUtils.getPreviousWord("abc def ", sSeparators), "abc");
+        assertEquals(EditingUtils.getPreviousWord("abc def.", sSeparators), "abc");
+        assertEquals(EditingUtils.getPreviousWord("abc def .", sSeparators), "def");
+        assertNull(EditingUtils.getPreviousWord("abc ", sSeparators));
+    }
+
+    /**
+     * Test for getting the word before the cursor (for bigram)
+     */
+    public void testGetThisWord() {
+        assertEquals(EditingUtils.getThisWord("abc def", sSeparators), "def");
+        assertEquals(EditingUtils.getThisWord("abc def ", sSeparators), "def");
+        assertNull(EditingUtils.getThisWord("abc def.", sSeparators));
+        assertNull(EditingUtils.getThisWord("abc def .", sSeparators));
+    }
+}