Merge "Split the sentences into words ourselves"
diff --git a/dictionaries/en_GB_wordlist.combined.gz b/dictionaries/en_GB_wordlist.combined.gz
index a93d834..bf637e9 100644
--- a/dictionaries/en_GB_wordlist.combined.gz
+++ b/dictionaries/en_GB_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/en_US_wordlist.combined.gz b/dictionaries/en_US_wordlist.combined.gz
index 6925fbd..9ea04b1 100644
--- a/dictionaries/en_US_wordlist.combined.gz
+++ b/dictionaries/en_US_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/en_wordlist.combined.gz b/dictionaries/en_wordlist.combined.gz
index 9e19274..87a8633 100644
--- a/dictionaries/en_wordlist.combined.gz
+++ b/dictionaries/en_wordlist.combined.gz
Binary files differ
diff --git a/java/res/layout/emoji_keyboard_tab_icon.xml b/java/res/layout/emoji_keyboard_tab_icon.xml
index 13bb41c..15f9c3a 100644
--- a/java/res/layout/emoji_keyboard_tab_icon.xml
+++ b/java/res/layout/emoji_keyboard_tab_icon.xml
@@ -19,6 +19,8 @@
 -->
 
 <!-- Note: contentDescription will be added programatically in {@link EmojiPalettesView}. -->
+<!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+     We just need to ignore the system's audio and haptic feedback settings. -->
 <ImageView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="0dip"
     android:layout_weight="1.0"
@@ -26,4 +28,6 @@
     android:gravity="center"
     android:scaleType="center"
     android:contentDescription="@null"
+    android:hapticFeedbackEnabled="false"
+    android:soundEffectsEnabled="false"
 />
diff --git a/java/res/layout/emoji_palettes_view.xml b/java/res/layout/emoji_palettes_view.xml
index 06a937b..9ff090a 100644
--- a/java/res/layout/emoji_palettes_view.xml
+++ b/java/res/layout/emoji_palettes_view.xml
@@ -62,11 +62,15 @@
             android:layout_height="match_parent"
             android:background="@drawable/suggestions_strip_divider" />
         <!-- TODO: Implement KeyView and replace this. -->
+        <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+             We just need to ignore the system's audio and haptic feedback settings. -->
         <ImageButton
             android:id="@+id/emoji_keyboard_delete"
             android:layout_width="0dip"
             android:layout_weight="12.5"
             android:layout_height="match_parent"
+            android:hapticFeedbackEnabled="false"
+            android:soundEffectsEnabled="false"
             android:contentDescription="@string/spoken_description_delete" />
     </LinearLayout>
     <android.support.v4.view.ViewPager
@@ -85,18 +89,26 @@
         android:layout_weight="1"
     >
         <!-- TODO: Implement a KeyView and replace this. -->
+        <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+             We just need to ignore the system's audio and haptic feedback settings. -->
         <TextView
             android:id="@+id/emoji_keyboard_alphabet_left"
             android:layout_width="0dip"
             android:layout_weight="0.15"
             android:gravity="center"
-            android:layout_height="match_parent" />
+            android:layout_height="match_parent"
+            android:hapticFeedbackEnabled="false"
+            android:soundEffectsEnabled="false" />
         <!-- TODO: Implement KeyView and replace this. -->
+        <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+             We just need to ignore the system's audio and haptic feedback settings. -->
         <RelativeLayout
             android:id="@+id/emoji_keyboard_space"
             android:layout_width="0dip"
             android:layout_weight="0.70"
             android:layout_height="match_parent"
+            android:hapticFeedbackEnabled="false"
+            android:soundEffectsEnabled="false"
             android:contentDescription="@string/spoken_description_space">
             <!-- WORKAROUND: Show the spacebar icon as a bacground of this View. -->
             <View
@@ -108,11 +120,15 @@
                 android:layout_centerInParent="true" />
         </RelativeLayout>
         <!-- TODO: Implement KeyView and replace this. -->
+        <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+             We just need to ignore the system's audio and haptic feedback settings. -->
         <TextView
             android:id="@+id/emoji_keyboard_alphabet_right"
             android:layout_width="0dip"
             android:layout_weight="0.15"
             android:gravity="center"
-            android:layout_height="match_parent" />
+            android:layout_height="match_parent"
+            android:hapticFeedbackEnabled="false"
+            android:soundEffectsEnabled="false" />
     </LinearLayout>
 </com.android.inputmethod.keyboard.emoji.EmojiPalettesView>
diff --git a/java/res/layout/suggestion_divider.xml b/java/res/layout/suggestion_divider.xml
index 1490951..563599d 100644
--- a/java/res/layout/suggestion_divider.xml
+++ b/java/res/layout/suggestion_divider.xml
@@ -18,11 +18,17 @@
 */
 -->
 
+<!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+     We just need to ignore the system's audio and haptic feedback settings. -->
 <ImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="match_parent"
+    android:padding="0dp"
+    android:gravity="center"
     android:src="@drawable/suggestions_strip_divider"
     android:contentDescription="@null"
-    android:padding="0dp"
-    android:gravity="center" />
+    android:clickable="false"
+    android:longClickable="false"
+    android:hapticFeedbackEnabled="false"
+    android:soundEffectsEnabled="false" />
diff --git a/java/res/layout/suggestions_strip.xml b/java/res/layout/suggestions_strip.xml
index 3d2f07f..4894779 100644
--- a/java/res/layout/suggestions_strip.xml
+++ b/java/res/layout/suggestions_strip.xml
@@ -20,13 +20,19 @@
 
 <merge
     xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+         We just need to ignore the system's audio and haptic feedback settings. -->
     <LinearLayout
         android:id="@+id/suggestions_strip"
         android:orientation="horizontal"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_marginLeft="@dimen/config_suggestions_strip_horizontal_margin"
-        android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin" />
+        android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin"
+        android:hapticFeedbackEnabled="false"
+        android:soundEffectsEnabled="false" />
+    <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+         We just need to ignore the system's audio and haptic feedback settings. -->
     <LinearLayout
         android:id="@+id/add_to_dictionary_strip"
         android:orientation="horizontal"
@@ -34,7 +40,8 @@
         android:layout_height="match_parent"
         android:layout_marginLeft="@dimen/config_suggestions_strip_horizontal_margin"
         android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin"
-        android:visibility="invisible">
+        android:hapticFeedbackEnabled="false"
+        android:soundEffectsEnabled="false">
         <TextView
             android:id="@+id/word_to_save"
             android:layout_width="match_parent"
@@ -49,13 +56,17 @@
             android:textAlignment="viewStart"
             style="?attr/suggestionWordStyle" />
     </LinearLayout>
+    <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+         We just need to ignore the system's audio and haptic feedback settings. -->
     <LinearLayout
         android:id="@+id/important_notice_strip"
         android:orientation="horizontal"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_marginLeft="@dimen/config_suggestions_strip_horizontal_margin"
-        android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin">
+        android:layout_marginRight="@dimen/config_suggestions_strip_horizontal_margin"
+        android:hapticFeedbackEnabled="false"
+        android:soundEffectsEnabled="false" >
         <TextView
             android:id="@+id/important_notice_title"
             android:layout_width="match_parent"
diff --git a/java/res/raw/main_en.dict b/java/res/raw/main_en.dict
index d0ccdbb..d631d6f 100644
--- a/java/res/raw/main_en.dict
+++ b/java/res/raw/main_en.dict
Binary files differ
diff --git a/java/res/values-kk/strings-talkback-descriptions.xml b/java/res/values-kk/strings-talkback-descriptions.xml
index 13adf83..39388a4 100644
--- a/java/res/values-kk/strings-talkback-descriptions.xml
+++ b/java/res/values-kk/strings-talkback-descriptions.xml
@@ -27,7 +27,8 @@
     <skip />
     <!-- no translation found for spoken_auto_correct_obscured (6276420476908833791) -->
     <skip />
-    <string name="spoken_description_unknown" msgid="3197434010402179157">"Перне коды %d"</string>
+    <!-- no translation found for spoken_description_unknown (5139930082759824442) -->
+    <skip />
     <string name="spoken_description_shift" msgid="244197883292549308">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift қосулы (өшіру үшін түрту)"</string>
     <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Caps lock қосулы (өшіру үшін түрту)"</string>
diff --git a/java/res/values/themes-common.xml b/java/res/values/themes-common.xml
index df26fb3..02a93ca 100644
--- a/java/res/values/themes-common.xml
+++ b/java/res/values/themes-common.xml
@@ -124,9 +124,10 @@
         <item name="android:paddingTop">0dp</item>
         <item name="android:paddingRight">@dimen/config_suggestion_text_horizontal_padding</item>
         <item name="android:paddingBottom">0dp</item>
-        <!-- Provide a haptic feedback by ourselves based on the keyboard settings.
-             We just need to ignore the system's haptic feedback settings. -->
+        <!-- Provide audio and haptic feedback by ourselves based on the keyboard settings.
+             We just need to ignore the system's audio and haptic feedback settings. -->
         <item name="android:hapticFeedbackEnabled">false</item>
+        <item name="android:soundEffectsEnabled">false</item>
         <item name="android:focusable">false</item>
         <item name="android:clickable">false</item>
         <item name="android:singleLine">true</item>
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 3a64531..7a3510e 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -28,6 +28,7 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.Locale;
 
@@ -79,14 +80,6 @@
     /**
      * Returns the localized description of the action performed by a specified
      * key based on the current keyboard state.
-     * <p>
-     * The order of precedence for key descriptions is:
-     * <ol>
-     * <li>Manually-defined based on the key label</li>
-     * <li>Automatic or manually-defined based on the key code</li>
-     * <li>Automatically based on the key label</li>
-     * <li>{code null} for keys with no label or key code defined</li>
-     * </p>
      *
      * @param context The package's context.
      * @param keyboard The keyboard on which the key resides.
@@ -121,7 +114,20 @@
 
         // Just attempt to speak the description.
         if (code != Constants.CODE_UNSPECIFIED) {
-            return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
+            // If the key description should be obscured, now is the time to do it.
+            final boolean isDefinedNonCtrl = Character.isDefined(code)
+                    && !Character.isISOControl(code);
+            if (shouldObscure && isDefinedNonCtrl) {
+                return context.getString(OBSCURED_KEY_RES_ID);
+            }
+            final String description = getDescriptionForCodePoint(context, code);
+            if (description != null) {
+                return description;
+            }
+            if (!TextUtils.isEmpty(key.getLabel())) {
+                return key.getLabel();
+            }
+            return context.getString(R.string.spoken_description_unknown);
         }
         return null;
     }
@@ -247,57 +253,35 @@
 
     /**
      * Returns a localized character sequence describing what will happen when
-     * the specified key is pressed based on its key code.
-     * <p>
-     * The order of precedence for key code descriptions is:
-     * <ol>
-     * <li>Manually-defined shift-locked description</li>
-     * <li>Manually-defined shifted description</li>
-     * <li>Manually-defined normal description</li>
-     * <li>Automatic based on the character represented by the key code</li>
-     * <li>Fall-back for undefined or control characters</li>
-     * </ol>
-     * </p>
+     * the specified key is pressed based on its key code point.
      *
      * @param context The package's context.
-     * @param keyboard The keyboard on which the key resides.
-     * @param key The key from which to obtain a description.
-     * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured.
-     * @return a character sequence describing the action performed by pressing the key
+     * @param codePoint The code point from which to obtain a description.
+     * @return a character sequence describing the code point.
      */
-    private String getDescriptionForKeyCode(final Context context, final Keyboard keyboard,
-            final Key key, final boolean shouldObscure) {
-        final int code = key.getCode();
-
+    public String getDescriptionForCodePoint(final Context context, final int codePoint) {
         // If the key description should be obscured, now is the time to do it.
-        final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code);
-        if (shouldObscure && isDefinedNonCtrl) {
-            return context.getString(OBSCURED_KEY_RES_ID);
-        }
-        final int index = mKeyCodeMap.indexOfKey(code);
+        final int index = mKeyCodeMap.indexOfKey(codePoint);
         if (index >= 0) {
             return context.getString(mKeyCodeMap.valueAt(index));
         }
-        final String accentedLetter = getSpokenAccentedLetterDescription(context, code);
+        final String accentedLetter = getSpokenAccentedLetterDescription(context, codePoint);
         if (accentedLetter != null) {
             return accentedLetter;
         }
         // Here, <code>code</code> may be a base (non-accented) letter.
-        final String unsupportedSymbol = getSpokenSymbolDescription(context, code);
+        final String unsupportedSymbol = getSpokenSymbolDescription(context, codePoint);
         if (unsupportedSymbol != null) {
             return unsupportedSymbol;
         }
-        final String emojiDescription = getSpokenEmojiDescription(context, code);
+        final String emojiDescription = getSpokenEmojiDescription(context, codePoint);
         if (emojiDescription != null) {
             return emojiDescription;
         }
-        if (isDefinedNonCtrl) {
-            return Character.toString((char) code);
+        if (Character.isDefined(codePoint) && !Character.isISOControl(codePoint)) {
+            return StringUtils.newSingleCodePointString(codePoint);
         }
-        if (!TextUtils.isEmpty(key.getLabel())) {
-            return key.getLabel();
-        }
-        return context.getString(R.string.spoken_description_unknown, code);
+        return null;
     }
 
     // TODO: Remove this method once TTS supports those accented letters' verbalization.
diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
index 4fdf5b8..96f84dd 100644
--- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
+++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.accessibility;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -58,7 +59,8 @@
     /** The most recently set keyboard mode. */
     private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
     private static final int KEYBOARD_IS_HIDDEN = -1;
-    private boolean mShouldIgnoreOnRegisterHoverKey;
+    // The rectangle region to ignore hover events.
+    private final Rect mBoundsToIgnoreHoverEvent = new Rect();
 
     private final AccessibilityLongPressTimer mAccessibilityLongPressTimer;
 
@@ -154,14 +156,28 @@
         case KeyboardId.ELEMENT_ALPHABET:
             if (lastElementId == KeyboardId.ELEMENT_ALPHABET
                     || lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+                // Transition between alphabet mode and automatic shifted mode should be silently
+                // ignored because it can be determined by each key's talk back announce.
                 return;
             }
             resId = R.string.spoken_description_mode_alpha;
             break;
         case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+            if (lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) {
+                // Resetting automatic shifted mode by pressing the shift key causes the transition
+                // from automatic shifted to manual shifted that should be silently ignored.
+                return;
+            }
             resId = R.string.spoken_description_shiftmode_on;
             break;
         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+            if (lastElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) {
+                // Resetting caps locked mode by pressing the shift key causes the transition
+                // from shift locked to shift lock shifted that should be silently ignored.
+                return;
+            }
+            resId = R.string.spoken_description_shiftmode_locked;
+            break;
         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
             resId = R.string.spoken_description_shiftmode_locked;
             break;
@@ -192,31 +208,49 @@
 
     @Override
     protected void onRegisterHoverKey(final Key key, final MotionEvent event) {
+        final int x = key.getHitBox().centerX();
+        final int y = key.getHitBox().centerY();
         if (DEBUG_HOVER) {
-            Log.d(TAG, "onRegisterHoverKey: key=" + key + " ignore="
-                    + mShouldIgnoreOnRegisterHoverKey);
+            Log.d(TAG, "onRegisterHoverKey: key=" + key
+                    + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
         }
-        if (!mShouldIgnoreOnRegisterHoverKey) {
-            super.onRegisterHoverKey(key, event);
+        if (mBoundsToIgnoreHoverEvent.contains(x, y)) {
+            // This hover exit event points to the key that should be ignored.
+            // Clear the ignoring region to handle further hover events.
+            mBoundsToIgnoreHoverEvent.setEmpty();
+            return;
         }
-        mShouldIgnoreOnRegisterHoverKey = false;
+        super.onRegisterHoverKey(key, event);
     }
 
     @Override
     protected void onHoverEnterTo(final Key key) {
+        final int x = key.getHitBox().centerX();
+        final int y = key.getHitBox().centerY();
         if (DEBUG_HOVER) {
-            Log.d(TAG, "onHoverEnterTo: key=" + key);
+            Log.d(TAG, "onHoverEnterTo: key=" + key
+                    + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
         }
         mAccessibilityLongPressTimer.cancelLongPress();
+        if (mBoundsToIgnoreHoverEvent.contains(x, y)) {
+            return;
+        }
+        // This hover enter event points to the key that isn't in the ignoring region.
+        // Further hover events should be handled.
+        mBoundsToIgnoreHoverEvent.setEmpty();
         super.onHoverEnterTo(key);
         if (key.isLongPressEnabled()) {
             mAccessibilityLongPressTimer.startLongPress(key);
         }
     }
 
+    @Override
     protected void onHoverExitFrom(final Key key) {
+        final int x = key.getHitBox().centerX();
+        final int y = key.getHitBox().centerY();
         if (DEBUG_HOVER) {
-            Log.d(TAG, "onHoverExitFrom: key=" + key);
+            Log.d(TAG, "onHoverExitFrom: key=" + key
+                    + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y));
         }
         mAccessibilityLongPressTimer.cancelLongPress();
         super.onHoverExitFrom(key);
@@ -246,6 +280,24 @@
         // or a key invokes IME switcher dialog, we should just ignore the next
         // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether
         // {@link PointerTracker} is in operation or not.
-        mShouldIgnoreOnRegisterHoverKey = !tracker.isInOperation();
+        if (tracker.isInOperation()) {
+            // This long press shows a more keys keyboard and further hover events should be
+            // handled.
+            mBoundsToIgnoreHoverEvent.setEmpty();
+            return;
+        }
+        // This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}.
+        // We should ignore further hover events on this key.
+        mBoundsToIgnoreHoverEvent.set(key.getHitBox());
+        if (key.hasNoPanelAutoMoreKey()) {
+            // This long press has registered a code point without showing a more keys keyboard.
+            // We should talk back the code point if possible.
+            final int codePointOfNoPanelAutoMoreKey = key.getMoreKeys()[0].mCode;
+            final String text = KeyCodeDescriptionMapper.getInstance().getDescriptionForCodePoint(
+                    mKeyboardView.getContext(), codePointOfNoPanelAutoMoreKey);
+            if (text != null) {
+                sendWindowStateChanged(text);
+            }
+        }
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
index abed520..e37cd23 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
@@ -46,6 +46,7 @@
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -240,6 +241,8 @@
 
     @Override
     public void onTabChanged(final String tabId) {
+        AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
+                Constants.CODE_UNSPECIFIED, this);
         final int categoryId = mEmojiCategory.getCategoryId(tabId);
         setCurrentCategoryId(categoryId, false /* force */);
         updateEmojiCategoryPageIdView();
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 54bc295..eb8b34c 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -16,14 +16,14 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.latin.settings.SettingsValues;
-
 import android.content.Context;
 import android.media.AudioManager;
 import android.os.Vibrator;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
 
+import com.android.inputmethod.latin.settings.SettingsValues;
+
 /**
  * This class gathers audio feedback and haptic feedback functions.
  *
@@ -86,40 +86,41 @@
         if (mAudioManager == null) {
             return;
         }
-        if (mSoundOn) {
-            final int sound;
-            switch (code) {
-            case Constants.CODE_DELETE:
-                sound = AudioManager.FX_KEYPRESS_DELETE;
-                break;
-            case Constants.CODE_ENTER:
-                sound = AudioManager.FX_KEYPRESS_RETURN;
-                break;
-            case Constants.CODE_SPACE:
-                sound = AudioManager.FX_KEYPRESS_SPACEBAR;
-                break;
-            default:
-                sound = AudioManager.FX_KEYPRESS_STANDARD;
-                break;
-            }
-            mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume);
+        if (!mSoundOn) {
+            return;
         }
+        final int sound;
+        switch (code) {
+        case Constants.CODE_DELETE:
+            sound = AudioManager.FX_KEYPRESS_DELETE;
+            break;
+        case Constants.CODE_ENTER:
+            sound = AudioManager.FX_KEYPRESS_RETURN;
+            break;
+        case Constants.CODE_SPACE:
+            sound = AudioManager.FX_KEYPRESS_SPACEBAR;
+            break;
+        default:
+            sound = AudioManager.FX_KEYPRESS_STANDARD;
+            break;
+        }
+        mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume);
     }
 
     public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) {
         if (!mSettingsValues.mVibrateOn) {
             return;
         }
-        if (mSettingsValues.mKeypressVibrationDuration < 0) {
-            // Go ahead with the system default
-            if (viewToPerformHapticFeedbackOn != null) {
-                viewToPerformHapticFeedbackOn.performHapticFeedback(
-                        HapticFeedbackConstants.KEYBOARD_TAP,
-                        HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
-            }
+        if (mSettingsValues.mKeypressVibrationDuration >= 0) {
+            vibrate(mSettingsValues.mKeypressVibrationDuration);
             return;
         }
-        vibrate(mSettingsValues.mKeypressVibrationDuration);
+        // Go ahead with the system default
+        if (viewToPerformHapticFeedbackOn != null) {
+            viewToPerformHapticFeedbackOn.performHapticFeedback(
+                    HapticFeedbackConstants.KEYBOARD_TAP,
+                    HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+        }
     }
 
     public void onSettingsChanged(final SettingsValues settingsValues) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 48b6a46..bdf3923 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -574,6 +574,12 @@
             final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
         final ExpandableBinaryDictionary personalizationDict =
                 mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
+        if (personalizationDict == null) {
+            if (callback != null) {
+                callback.onFinished();
+            }
+            return;
+        }
         final ArrayList<LanguageModelParam> languageModelParams =
                 LanguageModelParam.createLanguageModelParamsFrom(
                         personalizationDataChunk.mTokens,
@@ -581,8 +587,7 @@
                         this /* dictionaryFacilitator */, spacingAndPunctuations,
                         new DistracterFilterCheckingIsInDictionary(
                                 mDistracterFilter, personalizationDict));
-        if (personalizationDict == null || languageModelParams == null
-                || languageModelParams.isEmpty()) {
+        if (languageModelParams == null || languageModelParams.isEmpty()) {
             if (callback != null) {
                 callback.onFinished();
             }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index e5e00d5..cf61855 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -49,6 +49,7 @@
  * queries in native code. This binary dictionary is written to internal storage.
  */
 abstract public class ExpandableBinaryDictionary extends Dictionary {
+    private static final boolean DEBUG = false;
 
     /** Used for Log actions from this class */
     private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName();
@@ -325,16 +326,17 @@
     protected void addNgramEntryLocked(final PrevWordsInfo prevWordsInfo, final String word,
             final int frequency, final int timestamp) {
         if (!mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp)) {
-            Log.e(TAG, "Cannot add n-gram entry.");
-            Log.e(TAG, "  PrevWordsInfo: " + prevWordsInfo);
-            Log.e(TAG, "  word: " + word);
+            if (DEBUG) {
+                Log.i(TAG, "Cannot add n-gram entry.");
+                Log.i(TAG, "  PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+            }
         }
     }
 
     /**
      * Dynamically remove the n-gram entry in the dictionary.
      */
-    public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word1) {
+    public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
             @Override
@@ -343,7 +345,12 @@
                     return;
                 }
                 runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                mBinaryDictionary.removeNgramEntry(prevWordsInfo, word1);
+                if (!mBinaryDictionary.removeNgramEntry(prevWordsInfo, word)) {
+                    if (DEBUG) {
+                        Log.i(TAG, "Cannot remove n-gram entry.");
+                        Log.i(TAG, "  PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+                    }
+                }
             }
         });
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 631d6b6..35966bb 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1374,34 +1374,15 @@
             callback.onGetSuggestedWords(SuggestedWords.EMPTY);
             return;
         }
-        // Get the word on which we should search the bigrams. If we are composing a word, it's
-        // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we
-        // should just skip whitespace if any, so 1.
         final SettingsValues currentSettings = mSettings.getCurrent();
         final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
-
-        if (DEBUG) {
-            if (mInputLogic.mWordComposer.isComposingWord()
-                    || mInputLogic.mWordComposer.isBatchMode()) {
-                final PrevWordsInfo prevWordsInfo
-                        = mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion();
-                // TODO: this is for checking consistency with older versions. Remove this when
-                // we are confident this is stable.
-                // We're checking the previous word in the text field against the memorized previous
-                // word. If we are composing a word we should have the second word before the cursor
-                // memorized, otherwise we should have the first.
-                final PrevWordsInfo rereadPrevWordsInfo =
-                        mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion(
-                                currentSettings.mSpacingAndPunctuations,
-                                mInputLogic.mWordComposer.isComposingWord() ? 2 : 1);
-                if (!TextUtils.equals(prevWordsInfo.mPrevWord, rereadPrevWordsInfo.mPrevWord)) {
-                    throw new RuntimeException("Unexpected previous word: "
-                            + prevWordsInfo.mPrevWord + " <> " + rereadPrevWordsInfo.mPrevWord);
-                }
-            }
-        }
         mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer,
-                mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion(),
+                mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion(
+                        currentSettings.mSpacingAndPunctuations,
+                        // Get the word on which we should search the bigrams. If we are composing
+                        // a word, it's whatever is *before* the half-committed word in the buffer,
+                        // hence 2; if we aren't, we should just skip whitespace if any, so 1.
+                        mInputLogic.mWordComposer.isComposingWord() ? 2 : 1),
                 keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive,
                 currentSettings.mAutoCorrectionEnabled, additionalFeaturesOptions, sessionId,
                 sequenceNumber, callback);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 9da0f84..1ba5d5e 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -110,7 +110,7 @@
                 wordComposer, prevWordsInfo, proximityInfo, blockOffensiveWords,
                 additionalFeaturesOptions, SESSION_TYPING, rawSuggestions);
 
-        final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
+        final boolean isOnlyFirstCharCapitalized = wordComposer.isOnlyFirstCharCapitalized();
         // If resumed, then we don't want to upcase everything: resuming on a fully-capitalized
         // words is rarely done to switch to another fully-capitalized word, but usually to a
         // normal, non-capitalized suggestion.
@@ -122,7 +122,7 @@
         } else {
             final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo(
                     suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase,
-                    isFirstCharCapitalized, trailingSingleQuotesCount);
+                    isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
             firstSuggestion = firstSuggestedWordInfo.mWord;
             if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
                 whitelistedWord = null;
@@ -142,7 +142,7 @@
         final boolean allowsToBeAutoCorrected = (null != whitelistedWord
                 && !whitelistedWord.equals(typedWord))
                 || (consideredWord.length() > 1 && !mDictionaryFacilitator.isValidWord(
-                        consideredWord, wordComposer.isFirstCharCapitalized())
+                        consideredWord, isOnlyFirstCharCapitalized)
                         && !typedWord.equals(firstSuggestion));
 
         final boolean hasAutoCorrection;
@@ -173,12 +173,12 @@
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
                 new ArrayList<>(suggestionResults);
         final int suggestionsCount = suggestionsContainer.size();
-        if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
+        if (isOnlyFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
             for (int i = 0; i < suggestionsCount; ++i) {
                 final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
                 final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
-                        wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
-                        trailingSingleQuotesCount);
+                        wordInfo, suggestionResults.mLocale, isAllUpperCase,
+                        isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
                 suggestionsContainer.set(i, transformedWordInfo);
             }
         }
@@ -292,11 +292,11 @@
 
     /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
             final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
-            final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
+            final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) {
         final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
         if (isAllUpperCase) {
             sb.append(wordInfo.mWord.toUpperCase(locale));
-        } else if (isFirstCharCapitalized) {
+        } else if (isOnlyFirstCharCapitalized) {
             sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
         } else {
             sb.append(wordInfo.mWord);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 7a50d1a..6ce1f85 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -45,9 +45,6 @@
     // The list of events that served to compose this string.
     private final ArrayList<Event> mEvents;
     private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
-    // The information of previous words (before the composing word). Must not be null. Used as
-    // context for suggestions.
-    private PrevWordsInfo mPrevWordsInfo;
     private String mAutoCorrection;
     private boolean mIsResumed;
     private boolean mIsBatchMode;
@@ -72,9 +69,9 @@
     private int mCursorPositionWithinWord;
 
     /**
-     * Whether the user chose to capitalize the first char of the word.
+     * Whether the composing word has the only first char capitalized.
      */
-    private boolean mIsFirstCharCapitalized;
+    private boolean mIsOnlyFirstCharCapitalized;
 
     public WordComposer() {
         mCombinerChain = new CombinerChain("");
@@ -84,7 +81,6 @@
         mIsBatchMode = false;
         mCursorPositionWithinWord = 0;
         mRejectedBatchModeSuggestion = null;
-        mPrevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
         refreshTypedWordCache();
     }
 
@@ -111,12 +107,11 @@
         mAutoCorrection = null;
         mCapsCount = 0;
         mDigitsCount = 0;
-        mIsFirstCharCapitalized = false;
+        mIsOnlyFirstCharCapitalized = false;
         mIsResumed = false;
         mIsBatchMode = false;
         mCursorPositionWithinWord = 0;
         mRejectedBatchModeSuggestion = null;
-        mPrevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
         refreshTypedWordCache();
     }
 
@@ -178,12 +173,6 @@
         return mInputPointers;
     }
 
-    private static boolean isFirstCharCapitalized(final int index, final int codePoint,
-            final boolean previous) {
-        if (index == 0) return Character.isUpperCase(codePoint);
-        return previous && !Character.isUpperCase(codePoint);
-    }
-
     /**
      * Process an input event.
      *
@@ -203,7 +192,7 @@
         mCursorPositionWithinWord = mCodePointSize;
         // We may have deleted the last one.
         if (0 == mCodePointSize) {
-            mIsFirstCharCapitalized = false;
+            mIsOnlyFirstCharCapitalized = false;
         }
         if (Constants.CODE_DELETE != event.mKeyCode) {
             if (newIndex < MAX_WORD_LENGTH) {
@@ -215,8 +204,12 @@
                     mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0);
                 }
             }
-            mIsFirstCharCapitalized = isFirstCharCapitalized(
-                    newIndex, primaryCode, mIsFirstCharCapitalized);
+            if (0 == newIndex) {
+                mIsOnlyFirstCharCapitalized = Character.isUpperCase(primaryCode);
+            } else {
+                mIsOnlyFirstCharCapitalized = mIsOnlyFirstCharCapitalized
+                        && !Character.isUpperCase(primaryCode);
+            }
             if (Character.isUpperCase(primaryCode)) mCapsCount++;
             if (Character.isDigit(primaryCode)) mDigitsCount++;
         }
@@ -296,10 +289,8 @@
      * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
      * @param codePoints the code points to set as the composing word.
      * @param coordinates the x, y coordinates of the key in the CoordinateUtils format
-     * @param prevWordsInfo the information of previous words, to use as context for suggestions
      */
-    public void setComposingWord(final int[] codePoints, final int[] coordinates,
-            final PrevWordsInfo prevWordsInfo) {
+    public void setComposingWord(final int[] codePoints, final int[] coordinates) {
         reset();
         final int length = codePoints.length;
         for (int i = 0; i < length; ++i) {
@@ -308,7 +299,6 @@
                     CoordinateUtils.yFromArray(coordinates, i)));
         }
         mIsResumed = true;
-        mPrevWordsInfo = prevWordsInfo;
     }
 
     /**
@@ -319,16 +309,13 @@
         return mTypedWordCache.toString();
     }
 
-    public PrevWordsInfo getPrevWordsInfoForSuggestion() {
-        return mPrevWordsInfo;
-    }
-
     /**
-     * Whether or not the user typed a capital letter as the first letter in the word
+     * Whether or not the user typed a capital letter as the first letter in the word, and no
+     * other letter is capitalized
      * @return capitalization preference
      */
-    public boolean isFirstCharCapitalized() {
-        return mIsFirstCharCapitalized;
+    public boolean isOnlyFirstCharCapitalized() {
+        return mIsOnlyFirstCharCapitalized;
     }
 
     /**
@@ -364,7 +351,7 @@
     }
 
     /**
-     * Saves the caps mode and the previous word at the start of composing.
+     * Saves the caps mode at the start of composing.
      *
      * WordComposer needs to know about the caps mode for several reasons. The first is, we need
      * to know after the fact what the reason was, to register the correct form into the user
@@ -373,12 +360,9 @@
      * Also, batch input needs to know about the current caps mode to display correctly
      * capitalized suggestions.
      * @param mode the mode at the time of start
-     * @param prevWordsInfo the information of previous words
      */
-    public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode,
-            final PrevWordsInfo prevWordsInfo) {
+    public void setCapitalizedModeAtStartComposingTime(final int mode) {
         mCapitalizedMode = mode;
-        mPrevWordsInfo = prevWordsInfo;
     }
 
     /**
@@ -429,11 +413,10 @@
         mCapsCount = 0;
         mDigitsCount = 0;
         mIsBatchMode = false;
-        mPrevWordsInfo = new PrevWordsInfo(committedWord.toString());
         mCombinerChain.reset();
         mEvents.clear();
         mCodePointSize = 0;
-        mIsFirstCharCapitalized = false;
+        mIsOnlyFirstCharCapitalized = false;
         mCapitalizedMode = CAPS_MODE_OFF;
         refreshTypedWordCache();
         mAutoCorrection = null;
@@ -443,15 +426,7 @@
         return lastComposedWord;
     }
 
-    // Call this when the recorded previous word should be discarded. This is typically called
-    // when the user inputs a separator that's not whitespace (including the case of the
-    // double-space-to-period feature).
-    public void discardPreviousWordForSuggestion() {
-        mPrevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
-    }
-
-    public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
-            final PrevWordsInfo prevWordsInfo) {
+    public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
         mEvents.clear();
         Collections.copy(mEvents, lastComposedWord.mEvents);
         mInputPointers.set(lastComposedWord.mInputPointers);
@@ -462,7 +437,6 @@
         mCursorPositionWithinWord = mCodePointSize;
         mRejectedBatchModeSuggestion = null;
         mIsResumed = true;
-        mPrevWordsInfo = prevWordsInfo;
     }
 
     public boolean isBatchMode() {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index c90dc90..24cc1ef 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -544,11 +544,8 @@
             }
         }
         mConnection.endBatchEdit();
-        mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
-                getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()),
-                // Prev word is 1st word before cursor
-                getPrevWordsInfoFromNthPreviousWordForSuggestion(
-                        settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
+        mWordComposer.setCapitalizedModeAtStartComposingTime(
+                getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
     }
 
     /* The sequence number member is only used in onUpdateBatchInput. It is increased each time
@@ -584,10 +581,8 @@
                     mSpaceState = SpaceState.PHANTOM;
                     keyboardSwitcher.requestUpdatingShiftState(
                             getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState());
-                    mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
-                            getActualCapsMode(settingsValues,
-                                    keyboardSwitcher.getKeyboardShiftMode()),
-                                            new PrevWordsInfo(commitParts[0]));
+                    mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode(
+                            settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
                     ++mAutoCommitSequenceNumber;
                 }
             }
@@ -724,15 +719,7 @@
             mWordComposer.processEvent(inputTransaction.mEvent);
             // If it's the first letter, make note of auto-caps state
             if (mWordComposer.isSingleLetter()) {
-                // We pass 2 to getPreviousWordForSuggestion when the previous code point is a word
-                // connector. Otherwise, we pass 1 because we were not composing a word yet, so the
-                // word we want is the 1st word before the cursor.
-                mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
-                        inputTransaction.mShiftState,
-                        getPrevWordsInfoFromNthPreviousWordForSuggestion(
-                                settingsValues.mSpacingAndPunctuations,
-                                settingsValues.mSpacingAndPunctuations.isWordConnector(
-                                        mConnection.getCodePointBeforeCursor()) ? 2 : 1));
+                mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState);
             }
             mConnection.setComposingText(getTextWithUnderline(
                     mWordComposer.getTypedWord()), 1);
@@ -924,10 +911,8 @@
                     // No need to reset mSpaceState, it has already be done (that's why we
                     // receive it as a parameter)
                     inputTransaction.setRequiresUpdateSuggestions();
-                    mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
-                            WordComposer.CAPS_MODE_OFF,
-                            getPrevWordsInfoFromNthPreviousWordForSuggestion(
-                                    inputTransaction.mSettingsValues.mSpacingAndPunctuations, 1));
+                    mWordComposer.setCapitalizedModeAtStartComposingTime(
+                            WordComposer.CAPS_MODE_OFF);
                     return;
                 }
             } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
@@ -1107,7 +1092,6 @@
             final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
                     .mSentenceSeparatorAndSpace;
             mConnection.commitText(textToInsert, 1);
-            mWordComposer.discardPreviousWordForSuggestion();
             return true;
         }
         return false;
@@ -1267,10 +1251,7 @@
         final int expectedCursorPosition = mConnection.getExpectedSelectionStart();
         if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) {
             // Show predictions.
-            mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
-                    WordComposer.CAPS_MODE_OFF,
-                    getPrevWordsInfoFromNthPreviousWordForSuggestion(
-                            settingsValues.mSpacingAndPunctuations, 1));
+            mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF);
             mLatinIME.mHandler.postUpdateSuggestionStrip();
             return;
         }
@@ -1322,7 +1303,7 @@
                 settingsValues.mSpacingAndPunctuations,
                 0 == numberOfCharsInWordBeforeCursor ? 1 : 2);
         mWordComposer.setComposingWord(codePoints,
-                mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo);
+                mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
         mWordComposer.setCursorPositionWithinWord(
                 typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
         mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
@@ -1450,7 +1431,7 @@
             // with the typed word, so we need to resume suggestions right away.
             final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
             mWordComposer.setComposingWord(codePoints,
-                    mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo);
+                    mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
             mConnection.setComposingText(textToCommit, 1);
         }
         // Don't restart suggestion yet. We'll restart if the user deletes the separator.
@@ -1897,21 +1878,6 @@
         // strings.
         mLastComposedWord = mWordComposer.commitWord(commitType,
                 chosenWordWithSuggestions, separatorString, prevWordsInfo);
-        final boolean shouldDiscardPreviousWordForSuggestion;
-        if (0 == StringUtils.codePointCount(separatorString)) {
-            // Separator is 0-length, we can keep the previous word for suggestion. Either this
-            // was a manual pick or the language has no spaces in which case we want to keep the
-            // previous word, or it was the keyboard closing or the cursor moving in which case it
-            // will be reset anyway.
-            shouldDiscardPreviousWordForSuggestion = false;
-        } else {
-            // Otherwise, we discard if the separator contains any non-whitespace.
-            shouldDiscardPreviousWordForSuggestion =
-                    !StringUtils.containsOnlyWhitespace(separatorString);
-        }
-        if (shouldDiscardPreviousWordForSuggestion) {
-            mWordComposer.discardPreviousWordForSuggestion();
-        }
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index d7953e6..0032fcb 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -323,7 +323,7 @@
                 } else {
                     coordinates = dictInfo.mKeyboard.getCoordinates(codePoints);
                 }
-                composer.setComposingWord(codePoints, coordinates, null /* previousWord */);
+                composer.setComposingWord(codePoints, coordinates);
                 // TODO: make a spell checker option to block offensive words or not
                 final ArrayList<SuggestedWordInfo> suggestions =
                         dictInfo.mDictionary.getSuggestions(composer, prevWordsInfo,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 810bda7..19b48f0 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -379,10 +379,9 @@
         } else {
             wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
         }
-
-        // Disable this suggestion if the suggestion is null or empty.
-        // TODO: Fix disabled {@link TextView}'s content description.
-        wordView.setEnabled(!TextUtils.isEmpty(word));
+        // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack.
+        // Use a simple {@link String} to avoid the issue.
+        wordView.setContentDescription(TextUtils.isEmpty(word) ? null : word.toString());
         final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
         final float scaleX = getTextScaleX(word, width, wordView.getPaint());
         wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
@@ -461,14 +460,15 @@
             }
 
             final TextView wordView = mWordViews.get(positionInStrip);
-            wordView.setEnabled(true);
-            wordView.setTextColor(mColorAutoCorrect);
+            final String punctuation = punctuationSuggestions.getLabel(positionInStrip);
             // {@link TextView#getTag()} is used to get the index in suggestedWords at
             // {@link SuggestionStripView#onClick(View)}.
             wordView.setTag(positionInStrip);
-            wordView.setText(punctuationSuggestions.getLabel(positionInStrip));
+            wordView.setText(punctuation);
+            wordView.setContentDescription(punctuation);
             wordView.setTextScaleX(1.0f);
             wordView.setCompoundDrawables(null, null, null, null);
+            wordView.setTextColor(mColorAutoCorrect);
             stripView.addView(wordView);
             setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
         }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 9724149..3be933f 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -163,7 +163,6 @@
             word.setOnLongClickListener(this);
             mWordViews.add(word);
             final View divider = inflater.inflate(R.layout.suggestion_divider, null);
-            divider.setOnClickListener(this);
             mDividerViews.add(divider);
             final TextView info = new TextView(context, null, R.attr.suggestionWordStyle);
             info.setTextColor(Color.WHITE);
@@ -429,6 +428,8 @@
 
     @Override
     public void onClick(final View view) {
+        AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
+                Constants.CODE_UNSPECIFIED, this);
         if (view == mImportantNoticeStrip) {
             mListener.showImportantNoticeContents();
             return;
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
index 1f1475a..0ee6236 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
@@ -22,6 +22,7 @@
 
 import android.content.Context;
 import android.util.Log;
+import android.util.LruCache;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.latin.DictionaryFacilitator;
@@ -36,9 +37,11 @@
     private static final boolean DEBUG = false;
 
     private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
+    private static final int MAX_DISTRACTERS_CACHE_SIZE = 512;
 
     private final Context mContext;
     private final DictionaryFacilitator mDictionaryFacilitator;
+    private final LruCache<String, Boolean> mDistractersCache;
     private final Object mLock = new Object();
 
     /**
@@ -49,6 +52,7 @@
     public DistracterFilterCheckingExactMatches(final Context context) {
         mContext = context;
         mDictionaryFacilitator = new DictionaryFacilitator();
+        mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
     }
 
     @Override
@@ -87,6 +91,7 @@
             synchronized (mLock) {
                 // Reset dictionaries for the locale.
                 try {
+                    mDistractersCache.evictAll();
                     loadDictionariesForLocale(locale);
                 } catch (final InterruptedException e) {
                     Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter",
@@ -95,6 +100,15 @@
                 }
             }
         }
+
+        final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
+        if (isCachedDistracter != null && isCachedDistracter) {
+            if (DEBUG) {
+                Log.d(TAG, "testedWord: " + testedWord);
+                Log.d(TAG, "isDistracter: true (cache hit)");
+            }
+            return true;
+        }
         // The tested word is a distracter when there is a word that is exact matched to the tested
         // word and its probability is higher than the tested word's probability.
         final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord);
@@ -106,6 +120,10 @@
             Log.d(TAG, "exactMatchFreq: " + exactMatchFreq);
             Log.d(TAG, "isDistracter: " + isDistracter);
         }
+        if (isDistracter) {
+            // Add the word to the cache.
+            mDistractersCache.put(testedWord, Boolean.TRUE);
+        }
         return isDistracter;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 4ed0f0a..e4237a7 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -315,24 +315,6 @@
         return true;
     }
 
-    /**
-     * Returns true if all code points in text are whitespace, false otherwise. Empty is true.
-     */
-    // Interestingly enough, U+00A0 NO-BREAK SPACE and U+200B ZERO-WIDTH SPACE are not considered
-    // whitespace, while EN SPACE, EM SPACE and IDEOGRAPHIC SPACES are.
-    public static boolean containsOnlyWhitespace(final String text) {
-        final int length = text.length();
-        int i = 0;
-        while (i < length) {
-            final int codePoint = text.codePointAt(i);
-            if (!Character.isWhitespace(codePoint)) {
-                return false;
-            }
-            i += Character.charCount(codePoint);
-        }
-        return true;
-    }
-
     public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
             final int[] sortedSeparators) {
         boolean needsCapsNext = true;
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
index 4f4f01c..f9d98af 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardThemeTests.java
@@ -83,10 +83,14 @@
      * Test keyboard theme preference on the same platform version and the same keyboard version.
      */
 
-    private void assertKeyboardThemePreference(final int sdkVersion, final int oldThemeId,
+    private void assertKeyboardThemePreference(final int sdkVersion, final int previousThemeId,
             final int expectedThemeId) {
+        // Clear preferences before testing.
+        setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+        setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
+        // Set the preference of the sdkVersion to previousThemeId.
         final String prefKey = KeyboardTheme.getPreferenceKey(sdkVersion);
-        setKeyboardThemePreference(prefKey, oldThemeId);
+        setKeyboardThemePreference(prefKey, previousThemeId);
         assertKeyboardTheme(sdkVersion, expectedThemeId);
     }
 
@@ -127,10 +131,10 @@
      * Test default keyboard theme based on the platform version.
      */
 
-    private void assertDefaultKeyboardTheme(final int sdkVersion, final int oldThemeId,
+    private void assertDefaultKeyboardTheme(final int sdkVersion, final int previousThemeId,
             final int expectedThemeId) {
         final String oldPrefKey = KeyboardTheme.KLP_KEYBOARD_THEME_KEY;
-        setKeyboardThemePreference(oldPrefKey, oldThemeId);
+        setKeyboardThemePreference(oldPrefKey, previousThemeId);
 
         final KeyboardTheme defaultTheme =
                 KeyboardTheme.getDefaultKeyboardTheme(mPrefs, sdkVersion);
@@ -139,7 +143,7 @@
         assertEquals(expectedThemeId, defaultTheme.mThemeId);
         if (sdkVersion <= VERSION_CODES.KITKAT) {
             // Old preference must be retained if it is valid. Otherwise it must be pruned.
-            assertEquals(isValidKeyboardThemeId(oldThemeId), mPrefs.contains(oldPrefKey));
+            assertEquals(isValidKeyboardThemeId(previousThemeId), mPrefs.contains(oldPrefKey));
             return;
         }
         // Old preference must be removed.
@@ -181,9 +185,9 @@
      * to the keyboard that supports LXX theme.
      */
 
-    private void assertUpgradeKeyboardToLxxOn(final int sdkVersion, final int oldThemeId,
+    private void assertUpgradeKeyboardToLxxOn(final int sdkVersion, final int previousThemeId,
             final int expectedThemeId) {
-        setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, oldThemeId);
+        setKeyboardThemePreference(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, previousThemeId);
         // Clean up new keyboard theme preference to simulate "upgrade to LXX keyboard".
         setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
 
@@ -195,9 +199,9 @@
             // New preference must not exist.
             assertFalse(mPrefs.contains(KeyboardTheme.LXX_KEYBOARD_THEME_KEY));
             // Old preference must be retained if it is valid. Otherwise it must be pruned.
-            assertEquals(isValidKeyboardThemeId(oldThemeId),
+            assertEquals(isValidKeyboardThemeId(previousThemeId),
                     mPrefs.contains(KeyboardTheme.KLP_KEYBOARD_THEME_KEY));
-            if (isValidKeyboardThemeId(oldThemeId)) {
+            if (isValidKeyboardThemeId(previousThemeId)) {
                 // Old preference must have an expected value.
                 assertEquals(mPrefs.getString(KeyboardTheme.KLP_KEYBOARD_THEME_KEY, null),
                         Integer.toString(expectedThemeId));
@@ -247,7 +251,7 @@
      */
 
     private void assertUpgradePlatformFromTo(final int oldSdkVersion, final int newSdkVersion,
-            final int oldThemeId, final int expectedThemeId) {
+            final int previousThemeId, final int expectedThemeId) {
         if (newSdkVersion < oldSdkVersion) {
             // No need to test.
             return;
@@ -257,7 +261,7 @@
         setKeyboardThemePreference(KeyboardTheme.LXX_KEYBOARD_THEME_KEY, THEME_ID_NULL);
 
         final String oldPrefKey = KeyboardTheme.getPreferenceKey(oldSdkVersion);
-        setKeyboardThemePreference(oldPrefKey, oldThemeId);
+        setKeyboardThemePreference(oldPrefKey, previousThemeId);
 
         assertKeyboardTheme(newSdkVersion, expectedThemeId);
     }
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
index 274555a..c44544f 100644
--- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -40,8 +40,7 @@
         final int[] COORDINATES_WITHIN_BMP =
                 CoordinateUtils.newCoordinateArray(CODEPOINTS_WITHIN_BMP.length,
                         Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-        final PrevWordsInfo PREV_WORDS_INFO = new PrevWordsInfo("prevword");
-        wc.setComposingWord(CODEPOINTS_WITHIN_BMP, COORDINATES_WITHIN_BMP, PREV_WORDS_INFO);
+        wc.setComposingWord(CODEPOINTS_WITHIN_BMP, COORDINATES_WITHIN_BMP);
         assertEquals(wc.size(), STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length()));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         wc.setCursorPositionWithinWord(2);
@@ -56,15 +55,12 @@
         // Move the cursor to after the 'f'
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
-        // Check the previous word is still there
-        assertEquals(PREV_WORDS_INFO, wc.getPrevWordsInfoForSuggestion());
         // Move the cursor past the end of the word
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15));
         // Do what LatinIME does when the cursor is moved outside of the word,
         // and check the behavior is correct.
         wc.reset();
-        assertNull(wc.getPrevWordsInfoForSuggestion().mPrevWord);
 
         // \uD861\uDED7 is 𨛗, a character outside the BMP
         final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh";
@@ -73,8 +69,8 @@
         final int[] COORDINATES_WITH_SUPPLEMENTARY_CHAR =
                 CoordinateUtils.newCoordinateArray(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length,
                         Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PrevWordsInfo.EMPTY_PREV_WORDS_INFO);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         assertEquals(wc.size(), CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length);
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         wc.setCursorPositionWithinWord(3);
@@ -83,55 +79,43 @@
         assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
-        assertNull(wc.getPrevWordsInfoForSuggestion().mPrevWord);
 
-        final PrevWordsInfo PREV_WORDS_INFO_STR_WITHIN_BMP = new PrevWordsInfo(STR_WITHIN_BMP);
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PREV_WORDS_INFO_STR_WITHIN_BMP);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
-        assertEquals(PREV_WORDS_INFO_STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion());
 
-        final PrevWordsInfo PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR =
-                new PrevWordsInfo(STR_WITH_SUPPLEMENTARY_CHAR);
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
-        assertEquals(PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR,
-                wc.getPrevWordsInfoForSuggestion());
 
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PREV_WORDS_INFO_STR_WITHIN_BMP);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1));
-        assertEquals(PREV_WORDS_INFO_STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion());
 
 
-        final PrevWordsInfo PREV_WORDS_INFO_NULL = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PREV_WORDS_INFO_NULL);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         wc.setCursorPositionWithinWord(3);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9));
-        assertNull(wc.getPrevWordsInfoForSuggestion().mPrevWord);
 
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10));
-        assertEquals(PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR,
-                wc.getPrevWordsInfoForSuggestion());
 
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PREV_WORDS_INFO_NULL);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11));
 
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PREV_WORDS_INFO_NULL);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
 
-        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                PREV_WORDS_INFO_NULL);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR,
+                COORDINATES_WITH_SUPPLEMENTARY_CHAR);
         wc.setCursorPositionWithinWord(2);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
     }
diff --git a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
index bdc608a..cd9a983 100644
--- a/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringAndJsonUtilsTests.java
@@ -285,20 +285,6 @@
         assertTrue(bytesStr.equals(bytesStr2));
     }
 
-    public void testContainsOnlyWhitespace() {
-        assertTrue(StringUtils.containsOnlyWhitespace("   "));
-        assertTrue(StringUtils.containsOnlyWhitespace(""));
-        assertTrue(StringUtils.containsOnlyWhitespace("  \n\t\t"));
-        // U+2002 : EN SPACE
-        // U+2003 : EM SPACE
-        // U+3000 : IDEOGRAPHIC SPACE (commonly "double-width space")
-        assertTrue(StringUtils.containsOnlyWhitespace("\u2002\u2003\u3000"));
-        assertFalse(StringUtils.containsOnlyWhitespace("  a "));
-        assertFalse(StringUtils.containsOnlyWhitespace(". "));
-        assertFalse(StringUtils.containsOnlyWhitespace("."));
-        assertTrue(StringUtils.containsOnlyWhitespace(""));
-    }
-
     public void testJsonUtils() {
         final Object[] objs = new Object[] { 1, "aaa", "bbb", 3 };
         final List<Object> objArray = Arrays.asList(objs);