Merge "Make FormatUtils use ByteArrayView."
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index f02f788..ef5b047 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -257,6 +257,8 @@
 
     public boolean isConsumed() { return 0 != (FLAG_CONSUMED & mFlags); }
 
+    public boolean isGesture() { return EVENT_TYPE_GESTURE == mEventType; }
+
     // Returns whether this is a fake key press from the suggestion strip. This happens with
     // punctuation signs selected from the suggestion strip.
     public boolean isSuggestionStripPress() {
diff --git a/java/src/com/android/inputmethod/event/InputTransaction.java b/java/src/com/android/inputmethod/event/InputTransaction.java
index b18bf56..5bc9111 100644
--- a/java/src/com/android/inputmethod/event/InputTransaction.java
+++ b/java/src/com/android/inputmethod/event/InputTransaction.java
@@ -33,7 +33,7 @@
 
     // Initial conditions
     public final SettingsValues mSettingsValues;
-    private final Event mEvent;
+    public final Event mEvent;
     public final long mTimestamp;
     public final int mSpaceState;
     public final int mShiftState;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2cf7a04..71fd10e 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -138,7 +138,7 @@
                     new Runnable() {
                         @Override
                         public void run() {
-                            mHandler.postUpdateSuggestionStrip();
+                            mHandler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
                         }
                     });
     private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
@@ -220,7 +220,7 @@
             case MSG_UPDATE_SUGGESTION_STRIP:
                 cancelUpdateSuggestionStrip();
                 latinIme.mInputLogic.performUpdateSuggestionStripSync(
-                        latinIme.mSettings.getCurrent());
+                        latinIme.mSettings.getCurrent(), msg.arg1 /* inputStyle */);
                 break;
             case MSG_UPDATE_SHIFT_STATE:
                 switcher.requestUpdatingShiftState(latinIme.getCurrentAutoCapsState(),
@@ -270,8 +270,9 @@
             }
         }
 
-        public void postUpdateSuggestionStrip() {
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions);
+        public void postUpdateSuggestionStrip(final int inputStyle) {
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP, inputStyle,
+                    0 /* ignored */), mDelayUpdateSuggestions);
         }
 
         public void postReopenDictionaries() {
@@ -1060,7 +1061,8 @@
                         applicationSpecifiedCompletions);
         final SuggestedWords suggestedWords = new SuggestedWords(applicationSuggestedWords,
                 null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */,
-                false /* isObsoleteSuggestions */, false /* isPrediction */);
+                false /* isObsoleteSuggestions */, false /* isPrediction */,
+                SuggestedWords.INPUT_STYLE_APPLICATION_SPECIFIED /* inputStyle */);
         // When in fullscreen mode, show completions generated by the application forcibly
         setSuggestedWords(suggestedWords);
     }
@@ -1451,7 +1453,7 @@
     }
 
     // TODO[IL]: Move this out of LatinIME.
-    public void getSuggestedWords(final int sessionId, final int sequenceNumber,
+    public void getSuggestedWords(final int inputStyle, final int sequenceNumber,
             final OnGetSuggestedWordsCallback callback) {
         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
         if (keyboard == null) {
@@ -1459,7 +1461,7 @@
             return;
         }
         mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard.getProximityInfo(),
-                mKeyboardSwitcher.getKeyboardShiftMode(), sessionId, sequenceNumber, callback);
+                mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback);
     }
 
     @Override
@@ -1543,7 +1545,16 @@
         default: // SHIFT_NO_UPDATE
         }
         if (inputTransaction.requiresUpdateSuggestions()) {
-            mHandler.postUpdateSuggestionStrip();
+            final int inputStyle;
+            if (inputTransaction.mEvent.isSuggestionStripPress()) {
+                // Suggestion strip press: no input.
+                inputStyle = SuggestedWords.INPUT_STYLE_NONE;
+            } else if (inputTransaction.mEvent.isGesture()) {
+                inputStyle = SuggestedWords.INPUT_STYLE_TAIL_BATCH;
+            } else {
+                inputStyle = SuggestedWords.INPUT_STYLE_TYPING;
+            }
+            mHandler.postUpdateSuggestionStrip(inputStyle);
         }
         if (inputTransaction.didAffectContents()) {
             mSubtypeState.setCurrentSubtypeHasBeenUsed();
diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
index 0fba37c..6b0205c 100644
--- a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
@@ -35,7 +35,8 @@
                 false /* typedWordValid */,
                 false /* hasAutoCorrectionCandidate */,
                 false /* isObsoleteSuggestions */,
-                false /* isPrediction */);
+                false /* isPrediction */,
+                INPUT_STYLE_NONE /* inputStyle */);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index b8b6d64..ab852f8 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -40,13 +40,8 @@
     // Session id for
     // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}.
     // We are sharing the same ID between typing and gesture to save RAM footprint.
-    public static final int SESSION_TYPING = 0;
-    public static final int SESSION_GESTURE = 0;
-
-    // TODO: rename this to CORRECTION_OFF
-    public static final int CORRECTION_NONE = 0;
-    // TODO: rename this to CORRECTION_ON
-    public static final int CORRECTION_FULL = 1;
+    public static final int SESSION_ID_TYPING = 0;
+    public static final int SESSION_ID_GESTURE = 0;
 
     // Close to -2**31
     private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
@@ -75,14 +70,15 @@
     public void getSuggestedWords(final WordComposer wordComposer,
             final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
             final SettingsValuesForSuggestion settingsValuesForSuggestion,
-            final boolean isCorrectionEnabled, final int sessionId, final int sequenceNumber,
+            final boolean isCorrectionEnabled, final int inputStyle, final int sequenceNumber,
             final OnGetSuggestedWordsCallback callback) {
         if (wordComposer.isBatchMode()) {
             getSuggestedWordsForBatchInput(wordComposer, prevWordsInfo, proximityInfo,
-                    settingsValuesForSuggestion, sessionId, sequenceNumber, callback);
+                    settingsValuesForSuggestion, inputStyle, sequenceNumber, callback);
         } else {
-            getSuggestedWordsForTypingInput(wordComposer, prevWordsInfo, proximityInfo,
-                    settingsValuesForSuggestion, isCorrectionEnabled, sequenceNumber, callback);
+            getSuggestedWordsForNonBatchInput(wordComposer, prevWordsInfo, proximityInfo,
+                    settingsValuesForSuggestion, inputStyle, isCorrectionEnabled,
+                    sequenceNumber, callback);
         }
     }
 
@@ -120,11 +116,11 @@
         return firstSuggestedWordInfo.mWord;
     }
 
-    // Retrieves suggestions for the typing input
+    // Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
     // and calls the callback function with the suggestions.
-    private void getSuggestedWordsForTypingInput(final WordComposer wordComposer,
+    private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
             final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
-            final SettingsValuesForSuggestion settingsValuesForSuggestion,
+            final SettingsValuesForSuggestion settingsValuesForSuggestion, final int inputStyle,
             final boolean isCorrectionEnabled, final int sequenceNumber,
             final OnGetSuggestedWordsCallback callback) {
         final String typedWord = wordComposer.getTypedWord();
@@ -135,7 +131,7 @@
 
         final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
                 wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion,
-                SESSION_TYPING);
+                SESSION_ID_TYPING);
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
                 getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
                         trailingSingleQuotesCount);
@@ -197,7 +193,8 @@
                 // rename the attribute or change the value.
                 !resultsArePredictions && !allowsToBeAutoCorrected /* typedWordValid */,
                 hasAutoCorrection /* willAutoCorrect */,
-                false /* isObsoleteSuggestions */, resultsArePredictions, sequenceNumber));
+                false /* isObsoleteSuggestions */, resultsArePredictions,
+                inputStyle, sequenceNumber));
     }
 
     // Retrieves suggestions for the batch input
@@ -205,10 +202,11 @@
     private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
             final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
             final SettingsValuesForSuggestion settingsValuesForSuggestion,
-            final int sessionId, final int sequenceNumber,
+            final int inputStyle, final int sequenceNumber,
             final OnGetSuggestedWordsCallback callback) {
         final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
-                wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion, sessionId);
+                wordComposer, prevWordsInfo, proximityInfo, settingsValuesForSuggestion,
+                SESSION_ID_GESTURE);
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
                 new ArrayList<>(suggestionResults);
         final int suggestionsCount = suggestionsContainer.size();
@@ -246,7 +244,8 @@
                 true /* typedWordValid */,
                 false /* willAutoCorrect */,
                 false /* isObsoleteSuggestions */,
-                false /* isPrediction */, sequenceNumber));
+                false /* isPrediction */,
+                inputStyle, sequenceNumber));
     }
 
     private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 5231cc8..d7693af 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -31,12 +31,20 @@
     public static final int INDEX_OF_AUTO_CORRECTION = 1;
     public static final int NOT_A_SEQUENCE_NUMBER = -1;
 
+    public static final int INPUT_STYLE_NONE = 0;
+    public static final int INPUT_STYLE_TYPING = 1;
+    public static final int INPUT_STYLE_UPDATE_BATCH = 2;
+    public static final int INPUT_STYLE_TAIL_BATCH = 3;
+    public static final int INPUT_STYLE_APPLICATION_SPECIFIED = 4;
+    public static final int INPUT_STYLE_RECORRECTION = 5;
+
     // The maximum number of suggestions available.
     public static final int MAX_SUGGESTIONS = 18;
 
     private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = new ArrayList<>(0);
     public static final SuggestedWords EMPTY = new SuggestedWords(
-            EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, false, false, false, false);
+            EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, false, false, false, false,
+            INPUT_STYLE_NONE);
 
     public final String mTypedWord;
     public final boolean mTypedWordValid;
@@ -46,6 +54,9 @@
     public final boolean mWillAutoCorrect;
     public final boolean mIsObsoleteSuggestions;
     public final boolean mIsPrediction;
+    // How the input for these suggested words was done by the user. Must be one of the
+    // INPUT_STYLE_* constants above.
+    public final int mInputStyle;
     public final int mSequenceNumber; // Sequence number for auto-commit.
     protected final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList;
     public final ArrayList<SuggestedWordInfo> mRawSuggestions;
@@ -55,9 +66,10 @@
             final boolean typedWordValid,
             final boolean willAutoCorrect,
             final boolean isObsoleteSuggestions,
-            final boolean isPrediction) {
+            final boolean isPrediction,
+            final int inputStyle) {
         this(suggestedWordInfoList, rawSuggestions, typedWordValid, willAutoCorrect,
-                isObsoleteSuggestions, isPrediction, NOT_A_SEQUENCE_NUMBER);
+                isObsoleteSuggestions, isPrediction, inputStyle, NOT_A_SEQUENCE_NUMBER);
     }
 
     public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList,
@@ -66,11 +78,12 @@
             final boolean willAutoCorrect,
             final boolean isObsoleteSuggestions,
             final boolean isPrediction,
+            final int inputStyle,
             final int sequenceNumber) {
         this(suggestedWordInfoList, rawSuggestions,
                 (suggestedWordInfoList.isEmpty() || isPrediction) ? null
                         : suggestedWordInfoList.get(INDEX_OF_TYPED_WORD).mWord,
-                typedWordValid, willAutoCorrect, isObsoleteSuggestions, isPrediction,
+                typedWordValid, willAutoCorrect, isObsoleteSuggestions, isPrediction, inputStyle,
                 sequenceNumber);
     }
 
@@ -81,6 +94,7 @@
             final boolean willAutoCorrect,
             final boolean isObsoleteSuggestions,
             final boolean isPrediction,
+            final int inputStyle,
             final int sequenceNumber) {
         mSuggestedWordInfoList = suggestedWordInfoList;
         mRawSuggestions = rawSuggestions;
@@ -88,6 +102,7 @@
         mWillAutoCorrect = willAutoCorrect;
         mIsObsoleteSuggestions = isObsoleteSuggestions;
         mIsPrediction = isPrediction;
+        mInputStyle = inputStyle;
         mSequenceNumber = sequenceNumber;
         mTypedWord = typedWord;
     }
@@ -367,7 +382,7 @@
 
     // SuggestedWords is an immutable object, as much as possible. We must not just remove
     // words from the member ArrayList as some other parties may expect the object to never change.
-    public SuggestedWords getSuggestedWordsExcludingTypedWord() {
+    public SuggestedWords getSuggestedWordsExcludingTypedWord(final int inputStyle) {
         final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>();
         String typedWord = null;
         for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
@@ -383,7 +398,7 @@
         // no auto-correction should take place hence willAutoCorrect = false.
         return new SuggestedWords(newSuggestions, null /* rawSuggestions */, typedWord,
                 true /* typedWordValid */, false /* willAutoCorrect */, mIsObsoleteSuggestions,
-                mIsPrediction, NOT_A_SEQUENCE_NUMBER);
+                mIsPrediction, inputStyle, NOT_A_SEQUENCE_NUMBER);
     }
 
     // Creates a new SuggestedWordInfo from the currently suggested words that removes all but the
@@ -402,6 +417,7 @@
                     SuggestedWordInfo.NOT_A_CONFIDENCE));
         }
         return new SuggestedWords(newSuggestions, null /* rawSuggestions */, mTypedWordValid,
-                mWillAutoCorrect, mIsObsoleteSuggestions, mIsPrediction);
+                mWillAutoCorrect, mIsObsoleteSuggestions, mIsPrediction,
+                INPUT_STYLE_TAIL_BATCH);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 418866a..348bae6 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -216,7 +216,7 @@
         } else {
             resetComposingState(true /* alsoResetLastComposedWord */);
         }
-        handler.postUpdateSuggestionStrip();
+        handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_TYPING);
         final String text = performSpecificTldProcessingOnTextInput(rawText);
         if (SpaceState.PHANTOM == mSpaceState) {
             promotePhantomSpace(settingsValues);
@@ -288,9 +288,6 @@
             return inputTransaction;
         }
 
-        // We need to log before we commit, because the word composer will store away the user
-        // typed word.
-        final String replacedWord = mWordComposer.getTypedWord();
         commitChosenWord(settingsValues, suggestion,
                 LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
         mConnection.endBatchEdit();
@@ -311,7 +308,8 @@
             mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
         } else {
             // If we're not showing the "Touch again to save", then update the suggestion strip.
-            handler.postUpdateSuggestionStrip();
+            // That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
+            handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
         }
         return inputTransaction;
     }
@@ -1299,7 +1297,8 @@
                 prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
     }
 
-    public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
+    public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
+            final int inputStyle) {
         // Check if we have a suggestion engine attached.
         if (!settingsValues.needsToLookupSuggestions()) {
             if (mWordComposer.isComposingWord()) {
@@ -1317,8 +1316,8 @@
         }
 
         final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>();
-        mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
-                SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
+        mInputLogicHandler.getSuggestedWords(inputStyle, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
+                new OnGetSuggestedWordsCallback() {
                     @Override
                     public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
                         final String typedWord = mWordComposer.getTypedWord();
@@ -1379,7 +1378,7 @@
         if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) {
             // Show predictions.
             mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF);
-            mLatinIME.mHandler.postUpdateSuggestionStrip();
+            mLatinIME.mHandler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_RECORRECTION);
             return;
         }
         final TextRange range = mConnection.getWordRangeAtCursor(
@@ -1444,7 +1443,7 @@
             // If there weren't any suggestion spans on this word, suggestions#size() will be 1
             // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we
             // have no useful suggestions, so we will try to compute some for it instead.
-            mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
+            mInputLogicHandler.getSuggestedWords(Suggest.SESSION_ID_TYPING,
                     SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
                         @Override
                         public void onGetSuggestedWords(
@@ -1457,7 +1456,8 @@
                                 // case. The #getSuggestedWordsExcludingTypedWord() method sets
                                 // willAutoCorrect to false.
                                 suggestedWords = suggestedWordsIncludingTypedWord
-                                        .getSuggestedWordsExcludingTypedWord();
+                                        .getSuggestedWordsExcludingTypedWord(SuggestedWords
+                                                .INPUT_STYLE_RECORRECTION);
                             } else {
                                 // No saved suggestions, and we were unable to compute any good one
                                 // either. Rather than displaying an empty suggestion strip, we'll
@@ -1477,6 +1477,7 @@
                     null /* rawSuggestions */, typedWord,
                     false /* typedWordValid */, false /* willAutoCorrect */,
                     false /* isObsoleteSuggestions */, false /* isPrediction */,
+                    SuggestedWords.INPUT_STYLE_RECORRECTION,
                     SuggestedWords.NOT_A_SEQUENCE_NUMBER);
             mIsAutoCorrectionIndicatorOn = false;
             mLatinIME.mHandler.showSuggestionStrip(suggestedWords);
@@ -1773,7 +1774,8 @@
                 SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
         return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
                 false /* typedWordValid */, false /* hasAutoCorrectionCandidate */,
-                true /* isObsoleteSuggestions */, false /* isPrediction */);
+                true /* isObsoleteSuggestions */, false /* isPrediction */,
+                oldSuggestedWords.mInputStyle);
     }
 
     /**
@@ -1956,7 +1958,15 @@
         // Complete any pending suggestions query first
         if (handler.hasPendingUpdateSuggestions()) {
             handler.cancelUpdateSuggestionStrip();
-            performUpdateSuggestionStripSync(settingsValues);
+            // To know the input style here, we should retrieve the in-flight "update suggestions"
+            // message and read its arg1 member here. However, the Handler class does not let
+            // us retrieve this message, so we can't do that. But in fact, we notice that
+            // we only ever come here when the input style was typing. In the case of batch
+            // input, we update the suggestions synchronously when the tail batch comes. Likewise
+            // for application-specified completions. As for recorrections, we never auto-correct,
+            // so we don't come here either. Hence, the input style is necessarily
+            // INPUT_STYLE_TYPING.
+            performUpdateSuggestionStripSync(settingsValues, SuggestedWords.INPUT_STYLE_TYPING);
         }
         final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
         final String typedWord = mWordComposer.getTypedWord();
@@ -2052,7 +2062,7 @@
     }
 
     public void getSuggestedWords(final SettingsValues settingsValues,
-            final ProximityInfo proximityInfo, final int keyboardShiftMode, final int sessionId,
+            final ProximityInfo proximityInfo, final int keyboardShiftMode, final int inputStyle,
             final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
         mWordComposer.adviseCapitalizedModeBeforeFetchingSuggestions(
                 getActualCapsMode(settingsValues, keyboardShiftMode));
@@ -2068,6 +2078,6 @@
                         settingsValues.mPhraseGestureEnabled,
                         settingsValues.mAdditionalFeaturesSettingValues),
                 settingsValues.mAutoCorrectionEnabledPerUserSettings,
-                sessionId, sequenceNumber, callback);
+                inputStyle, sequenceNumber, callback);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
index 9dbe2c3..c6f83d0 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -96,7 +96,7 @@
     public boolean handleMessage(final Message msg) {
         switch (msg.what) {
             case MSG_GET_SUGGESTED_WORDS:
-                mLatinIME.getSuggestedWords(msg.arg1 /* sessionId */,
+                mLatinIME.getSuggestedWords(msg.arg1 /* inputStyle */,
                         msg.arg2 /* sequenceNumber */, (OnGetSuggestedWordsCallback) msg.obj);
                 break;
         }
@@ -134,7 +134,8 @@
                 return;
             }
             mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
-            getSuggestedWords(Suggest.SESSION_GESTURE, sequenceNumber,
+            getSuggestedWords(isTailBatchInput ? SuggestedWords.INPUT_STYLE_TAIL_BATCH
+                    : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber,
                     new OnGetSuggestedWordsCallback() {
                         @Override
                         public void onGetSuggestedWords(SuggestedWords suggestedWords) {
@@ -205,9 +206,9 @@
         updateBatchInput(batchPointers, sequenceNumber, true /* isTailBatchInput */);
     }
 
-    public void getSuggestedWords(final int sessionId, final int sequenceNumber,
+    public void getSuggestedWords(final int inputStyle, final int sequenceNumber,
             final OnGetSuggestedWordsCallback callback) {
         mNonUIThreadHandler.obtainMessage(
-                MSG_GET_SUGGESTED_WORDS, sessionId, sequenceNumber, callback).sendToTarget();
+                MSG_GET_SUGGESTED_WORDS, inputStyle, sequenceNumber, callback).sendToTarget();
     }
 }
diff --git a/native/jni/src/suggest/core/dictionary/error_type_utils.cpp b/native/jni/src/suggest/core/dictionary/error_type_utils.cpp
index b6bf7a9..1e2494e 100644
--- a/native/jni/src/suggest/core/dictionary/error_type_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/error_type_utils.cpp
@@ -19,17 +19,18 @@
 namespace latinime {
 
 const ErrorTypeUtils::ErrorType ErrorTypeUtils::NOT_AN_ERROR = 0x0;
-const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_CASE_ERROR = 0x1;
-const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_ACCENT_ERROR = 0x2;
-const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_DIGRAPH = 0x4;
-const ErrorTypeUtils::ErrorType ErrorTypeUtils::INTENTIONAL_OMISSION = 0x8;
-const ErrorTypeUtils::ErrorType ErrorTypeUtils::EDIT_CORRECTION = 0x10;
-const ErrorTypeUtils::ErrorType ErrorTypeUtils::PROXIMITY_CORRECTION = 0x20;
-const ErrorTypeUtils::ErrorType ErrorTypeUtils::COMPLETION = 0x40;
-const ErrorTypeUtils::ErrorType ErrorTypeUtils::NEW_WORD = 0x80;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_WRONG_CASE = 0x1;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_MISSING_ACCENT = 0x2;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_WRONG_ACCENT = 0x4;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::MATCH_WITH_DIGRAPH = 0x8;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::INTENTIONAL_OMISSION = 0x10;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::EDIT_CORRECTION = 0x20;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::PROXIMITY_CORRECTION = 0x40;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::COMPLETION = 0x80;
+const ErrorTypeUtils::ErrorType ErrorTypeUtils::NEW_WORD = 0x100;
 
 const ErrorTypeUtils::ErrorType ErrorTypeUtils::ERRORS_TREATED_AS_AN_EXACT_MATCH =
-        NOT_AN_ERROR | MATCH_WITH_CASE_ERROR | MATCH_WITH_ACCENT_ERROR | MATCH_WITH_DIGRAPH;
+        NOT_AN_ERROR | MATCH_WITH_WRONG_CASE | MATCH_WITH_MISSING_ACCENT | MATCH_WITH_DIGRAPH;
 
 const ErrorTypeUtils::ErrorType
         ErrorTypeUtils::ERRORS_TREATED_AS_AN_EXACT_MATCH_WITH_INTENTIONAL_OMISSION =
diff --git a/native/jni/src/suggest/core/dictionary/error_type_utils.h b/native/jni/src/suggest/core/dictionary/error_type_utils.h
index e3e76b2..fd1d5fc 100644
--- a/native/jni/src/suggest/core/dictionary/error_type_utils.h
+++ b/native/jni/src/suggest/core/dictionary/error_type_utils.h
@@ -30,8 +30,9 @@
     typedef uint32_t ErrorType;
 
     static const ErrorType NOT_AN_ERROR;
-    static const ErrorType MATCH_WITH_CASE_ERROR;
-    static const ErrorType MATCH_WITH_ACCENT_ERROR;
+    static const ErrorType MATCH_WITH_WRONG_CASE;
+    static const ErrorType MATCH_WITH_MISSING_ACCENT;
+    static const ErrorType MATCH_WITH_WRONG_ACCENT;
     static const ErrorType MATCH_WITH_DIGRAPH;
     // Treat error as an intentional omission when the CorrectionType is omission and the node can
     // be intentional omission.
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.cpp
index f7179f6..97a8bcc 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.cpp
@@ -425,6 +425,18 @@
     return true;
 }
 
+bool Ver4PatriciaTrieNodeWriter::suppressUnigramEntry(const PtNodeParams *const ptNodeParams) {
+    if (!mHeaderPolicy->hasHistoricalInfoOfWords()) {
+        // Require historical info to suppress unigram entry.
+        return false;
+    }
+    const HistoricalInfo suppressedHistorycalInfo(0 /* timestamp */, 0 /* level */, 0 /* count */);
+    const ProbabilityEntry probabilityEntryToWrite =
+            ProbabilityEntry().createEntryWithUpdatedHistoricalInfo(&suppressedHistorycalInfo);
+    return mBuffers->getMutableProbabilityDictContent()->setProbabilityEntry(
+            ptNodeParams->getTerminalId(), &probabilityEntryToWrite);
+}
+
 } // namespace v402
 } // namespace backward
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.h b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.h
index d49d9a6..9d8a55b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_writer.h
@@ -111,6 +111,11 @@
 
     bool updatePtNodeHasBigramsAndShortcutTargetsFlags(const PtNodeParams *const ptNodeParams);
 
+    // Suppress unigram not to use the word for generating suggestions. So, this method can be used
+    // only for dictionaries with historical info. Also, suppressed entries are included in unigram
+    // count. They will be removed from the dictionary during GC.
+    bool suppressUnigramEntry(const PtNodeParams *const ptNodeParams);
+
  private:
     DISALLOW_COPY_AND_ASSIGN(Ver4PatriciaTrieNodeWriter);
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
index 1296b8a..9c6452e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
@@ -210,7 +210,7 @@
     }
     for (const auto &shortcut : unigramProperty->getShortcuts()) {
         if (shortcut.getTargetCodePoints()->size() > MAX_WORD_LENGTH) {
-            AKLOGE("One of shortcut targets is too long to insert to the dictionary, length: %d",
+            AKLOGE("One of shortcut targets is too long to insert to the dictionary, length: %zd",
                     shortcut.getTargetCodePoints()->size());
             return false;
         }
@@ -245,7 +245,7 @@
                 if (!mUpdatingHelper.addShortcutTarget(wordPos,
                         shortcut.getTargetCodePoints()->data(),
                         shortcut.getTargetCodePoints()->size(), shortcut.getProbability())) {
-                    AKLOGE("Cannot add new shortcut target. PtNodePos: %d, length: %d, "
+                    AKLOGE("Cannot add new shortcut target. PtNodePos: %d, length: %zd, "
                             "probability: %d", wordPos, shortcut.getTargetCodePoints()->size(),
                             shortcut.getProbability());
                     return false;
@@ -258,6 +258,20 @@
     }
 }
 
+bool Ver4PatriciaTriePolicy::removeUnigramEntry(const int *const word, const int length) {
+    if (!mBuffers->isUpdatable()) {
+        AKLOGI("Warning: removeUnigramEntry() is called for non-updatable dictionary.");
+        return false;
+    }
+    const int ptNodePos = getTerminalPtNodePositionOfWord(word, length,
+            false /* forceLowerCaseSearch */);
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return false;
+    }
+    const PtNodeParams ptNodeParams = mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
+    return mNodeWriter.suppressUnigramEntry(&ptNodeParams);
+}
+
 bool Ver4PatriciaTriePolicy::addNgramEntry(const PrevWordsInfo *const prevWordsInfo,
         const BigramProperty *const bigramProperty) {
     if (!mBuffers->isUpdatable()) {
@@ -275,7 +289,7 @@
     }
     if (bigramProperty->getTargetCodePoints()->size() > MAX_WORD_LENGTH) {
         AKLOGE("The word is too long to insert the ngram to the dictionary. "
-                "length: %d", bigramProperty->getTargetCodePoints()->size());
+                "length: %zd", bigramProperty->getTargetCodePoints()->size());
         return false;
     }
     int prevWordsPtNodePos[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.h
index 9e989b2..d774996 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.h
@@ -108,10 +108,7 @@
     bool addUnigramEntry(const int *const word, const int length,
             const UnigramProperty *const unigramProperty);
 
-    bool removeUnigramEntry(const int *const word, const int length) {
-        // Removing unigram entry is not supported.
-        return false;
-    }
+    bool removeUnigramEntry(const int *const word, const int length);
 
     bool addNgramEntry(const PrevWordsInfo *const prevWordsInfo,
             const BigramProperty *const bigramProperty);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index 7238083..d8f4595 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -200,7 +200,7 @@
     }
     for (const auto &shortcut : unigramProperty->getShortcuts()) {
         if (shortcut.getTargetCodePoints()->size() > MAX_WORD_LENGTH) {
-            AKLOGE("One of shortcut targets is too long to insert to the dictionary, length: %d",
+            AKLOGE("One of shortcut targets is too long to insert to the dictionary, length: %zd",
                     shortcut.getTargetCodePoints()->size());
             return false;
         }
@@ -235,7 +235,7 @@
                 if (!mUpdatingHelper.addShortcutTarget(wordPos,
                         shortcut.getTargetCodePoints()->data(),
                         shortcut.getTargetCodePoints()->size(), shortcut.getProbability())) {
-                    AKLOGE("Cannot add new shortcut target. PtNodePos: %d, length: %d, "
+                    AKLOGE("Cannot add new shortcut target. PtNodePos: %d, length: %zd, "
                             "probability: %d", wordPos, shortcut.getTargetCodePoints()->size(),
                             shortcut.getProbability());
                     return false;
@@ -286,7 +286,7 @@
     }
     if (bigramProperty->getTargetCodePoints()->size() > MAX_WORD_LENGTH) {
         AKLOGE("The word is too long to insert the ngram to the dictionary. "
-                "length: %d", bigramProperty->getTargetCodePoints()->size());
+                "length: %zd", bigramProperty->getTargetCodePoints()->size());
         return false;
     }
     int prevWordsPtNodePos[MAX_PREV_WORD_COUNT_FOR_N_GRAM];
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
index 04cb660..52c4251 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
@@ -51,10 +51,10 @@
         }
         if (boostExactMatches && ErrorTypeUtils::isExactMatch(containedErrorTypes)) {
             score += ScoringParams::EXACT_MATCH_PROMOTION;
-            if ((ErrorTypeUtils::MATCH_WITH_CASE_ERROR & containedErrorTypes) != 0) {
+            if ((ErrorTypeUtils::MATCH_WITH_WRONG_CASE & containedErrorTypes) != 0) {
                 score -= ScoringParams::CASE_ERROR_PENALTY_FOR_EXACT_MATCH;
             }
-            if ((ErrorTypeUtils::MATCH_WITH_ACCENT_ERROR & containedErrorTypes) != 0) {
+            if ((ErrorTypeUtils::MATCH_WITH_MISSING_ACCENT & containedErrorTypes) != 0) {
                 score -= ScoringParams::ACCENT_ERROR_PENALTY_FOR_EXACT_MATCH;
             }
             if ((ErrorTypeUtils::MATCH_WITH_DIGRAPH & containedErrorTypes) != 0) {
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp b/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
index 54f65c7..1d590c3 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
@@ -36,25 +36,34 @@
                 // Compare the node code point with original primary code point on the keyboard.
                 const ProximityInfoState *const pInfoState =
                         traverseSession->getProximityInfoState(0);
-                const int primaryOriginalCodePoint = pInfoState->getPrimaryOriginalCodePointAt(
+                const int primaryCodePoint = pInfoState->getPrimaryCodePointAt(
                         dicNode->getInputIndex(0));
                 const int nodeCodePoint = dicNode->getNodeCodePoint();
-                if (primaryOriginalCodePoint == nodeCodePoint) {
+                // TODO: Check whether the input code point is on the keyboard.
+                if (primaryCodePoint == nodeCodePoint) {
                     // Node code point is same as original code point on the keyboard.
                     return ErrorTypeUtils::NOT_AN_ERROR;
-                } else if (CharUtils::toLowerCase(primaryOriginalCodePoint) ==
+                } else if (CharUtils::toLowerCase(primaryCodePoint) ==
                         CharUtils::toLowerCase(nodeCodePoint)) {
                     // Only cases of the code points are different.
-                    return ErrorTypeUtils::MATCH_WITH_CASE_ERROR;
-                } else if (CharUtils::toBaseCodePoint(primaryOriginalCodePoint) ==
-                        CharUtils::toBaseCodePoint(nodeCodePoint)) {
+                    return ErrorTypeUtils::MATCH_WITH_WRONG_CASE;
+                } else if (primaryCodePoint == CharUtils::toBaseCodePoint(nodeCodePoint)) {
                     // Node code point is a variant of original code point.
-                    return ErrorTypeUtils::MATCH_WITH_ACCENT_ERROR;
-                } else {
+                    return ErrorTypeUtils::MATCH_WITH_MISSING_ACCENT;
+                } else if (CharUtils::toBaseCodePoint(primaryCodePoint)
+                        == CharUtils::toBaseCodePoint(nodeCodePoint)) {
+                    // Base code points are the same but the code point is intentionally input.
+                    return ErrorTypeUtils::MATCH_WITH_WRONG_ACCENT;
+                } else if (CharUtils::toLowerCase(primaryCodePoint)
+                        == CharUtils::toBaseLowerCase(nodeCodePoint)) {
                     // Node code point is a variant of original code point and the cases are also
                     // different.
-                    return ErrorTypeUtils::MATCH_WITH_ACCENT_ERROR
-                            | ErrorTypeUtils::MATCH_WITH_CASE_ERROR;
+                    return ErrorTypeUtils::MATCH_WITH_MISSING_ACCENT
+                            | ErrorTypeUtils::MATCH_WITH_WRONG_CASE;
+                } else {
+                    // Base code points are the same and the cases are different.
+                    return ErrorTypeUtils::MATCH_WITH_WRONG_ACCENT
+                            | ErrorTypeUtils::MATCH_WITH_WRONG_CASE;
                 }
             }
             break;
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index ae18426..342eb29 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -689,4 +689,36 @@
         binaryDictionary.close();
         dictFile.delete();
     }
+
+    public void testRemoveUnigrams() {
+        for (final int formatVersion : DICT_FORMAT_VERSIONS) {
+            testRemoveUnigrams(formatVersion);
+        }
+    }
+
+    private void testRemoveUnigrams(final int formatVersion) {
+        final int unigramInputCount = 20;
+        setCurrentTimeForTestMode(mCurrentTime);
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary", formatVersion);
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        final BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        addUnigramWord(binaryDictionary, "aaa", Dictionary.NOT_A_PROBABILITY);
+        assertFalse(binaryDictionary.isValidWord("aaa"));
+        for (int i = 0; i < unigramInputCount; i++) {
+            addUnigramWord(binaryDictionary, "aaa", Dictionary.NOT_A_PROBABILITY);
+        }
+        assertTrue(binaryDictionary.isValidWord("aaa"));
+        assertTrue(binaryDictionary.removeUnigramEntry("aaa"));
+        assertFalse(binaryDictionary.isValidWord("aaa"));
+
+        binaryDictionary.close();
+        dictFile.delete();
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
index 66b4a9c..a5f20b5 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
@@ -48,7 +48,8 @@
                 false /* typedWordValid */,
                 false /* willAutoCorrect */,
                 false /* isObsoleteSuggestions */,
-                false /* isPrediction*/);
+                false /* isPrediction*/,
+                SuggestedWords.INPUT_STYLE_NONE);
         assertEquals(NUMBER_OF_ADDED_SUGGESTIONS + 1, words.size());
         assertEquals("typed", words.getWord(0));
         assertTrue(words.getInfo(0).isKindOf(SuggestedWordInfo.KIND_TYPED));
@@ -57,7 +58,8 @@
         assertEquals("4", words.getWord(5));
         assertTrue(words.getInfo(5).isKindOf(SuggestedWordInfo.KIND_CORRECTION));
 
-        final SuggestedWords wordsWithoutTyped = words.getSuggestedWordsExcludingTypedWord();
+        final SuggestedWords wordsWithoutTyped = words.getSuggestedWordsExcludingTypedWord(
+                SuggestedWords.INPUT_STYLE_NONE);
         assertEquals(words.size() - 1, wordsWithoutTyped.size());
         assertEquals("0", wordsWithoutTyped.getWord(0));
         assertTrue(wordsWithoutTyped.getInfo(0).isKindOf(SuggestedWordInfo.KIND_CORRECTION));
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index 14c9da0..abb468f 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -74,6 +74,23 @@
         }
     }
 
+    private void checkExistenceAndRemoveDictFile(final Locale locale, final File dictFile) {
+        Log.d(TAG, "waiting for writing ...");
+        waitForWriting(locale);
+        if (!dictFile.exists()) {
+            try {
+                Log.d(TAG, dictFile + " is not existing. Wait "
+                        + WAIT_FOR_WRITING_FILE_IN_MILLISECONDS + " ms for writing.");
+                printAllFiles(dictFile.getParentFile());
+                Thread.sleep(WAIT_FOR_WRITING_FILE_IN_MILLISECONDS);
+            } catch (final InterruptedException e) {
+                Log.e(TAG, "Interrupted during waiting for writing the dict file.");
+            }
+        }
+        assertTrue("check exisiting of " + dictFile, dictFile.exists());
+        FileUtils.deleteRecursively(dictFile);
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -210,10 +227,7 @@
             addAndWriteRandomWords(dummyLocale, numberOfWords, random,
                     true /* checksContents */);
         } finally {
-            Log.d(TAG, "waiting for writing ...");
-            waitForWriting(dummyLocale);
-            assertTrue("check exisiting of " + dictFile, dictFile.exists());
-            FileUtils.deleteRecursively(dictFile);
+            checkExistenceAndRemoveDictFile(dummyLocale, dictFile);
         }
     }
 
@@ -251,20 +265,15 @@
             Log.d(TAG, "testStressTestForSwitchingLanguageAndAddingWords took "
                     + (end - start) + " ms");
         } finally {
-            Log.d(TAG, "waiting for writing ...");
             for (int i = 0; i < numberOfLanguages; i++) {
-                waitForWriting(dummyLocales[i]);
-            }
-            for (final File dictFile : dictFiles) {
-                assertTrue("check exisiting of " + dictFile, dictFile.exists());
-                FileUtils.deleteRecursively(dictFile);
+                checkExistenceAndRemoveDictFile(dummyLocales[i], dictFiles[i]);
             }
         }
     }
 
     public void testAddManyWords() {
         final Locale dummyLocale =
-                new Locale(TEST_LOCALE_PREFIX + "random_words" + System.currentTimeMillis());
+                new Locale(TEST_LOCALE_PREFIX + "many_random_words" + System.currentTimeMillis());
         final String dictName = ExpandableBinaryDictionary.getDictName(
                 UserHistoryDictionary.NAME, dummyLocale, null /* dictFile */);
         final File dictFile = ExpandableBinaryDictionary.getDictFile(
@@ -275,20 +284,7 @@
         try {
             addAndWriteRandomWords(dummyLocale, numberOfWords, random, true /* checksContents */);
         } finally {
-            Log.d(TAG, "waiting for writing ...");
-            waitForWriting(dummyLocale);
-            if (!dictFile.exists()) {
-                try {
-                    Log.d(TAG, dictFile +" is not existing. Wait "
-                            + WAIT_FOR_WRITING_FILE_IN_MILLISECONDS + " ms for writing.");
-                    printAllFiles(dictFile.getParentFile());
-                    Thread.sleep(WAIT_FOR_WRITING_FILE_IN_MILLISECONDS);
-                } catch (final InterruptedException e) {
-                    Log.e(TAG, "Interrupted during waiting for writing the dict file.");
-                }
-            }
-            assertTrue("check exisiting of " + dictFile, dictFile.exists());
-            FileUtils.deleteRecursively(dictFile);
+            checkExistenceAndRemoveDictFile(dummyLocale, dictFile);
         }
     }