Merge "Debug log when failed to add/remove n-gram entry."
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/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/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/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/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/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);