Merge "Add backspace event methods to StatsUtils"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 2e81bdf..f1253b4 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -338,6 +338,8 @@
             <!-- If true, use functionalTextColor instead of ketTextColor to drawing the label on
                  the key -->
             <flag name="followFunctionalTextColor" value="0x80000" />
+            <!-- Keep aspect ratio of key background. -->
+            <flag name="keepBackgroundAspectRatio" value="0x100000" />
             <!-- If true, disable keyHintLabel. -->
             <flag name="disableKeyHintLabel" value="0x40000000" />
             <!-- If true, disable additionalMoreKeys. -->
diff --git a/java/res/values/themes-lxx-dark.xml b/java/res/values/themes-lxx-dark.xml
index 76e9d33..5b26813 100644
--- a/java/res/values/themes-lxx-dark.xml
+++ b/java/res/values/themes-lxx-dark.xml
@@ -114,6 +114,7 @@
         <item name="android:background">@android:color/transparent</item>
         <item name="keyBackground">@drawable/btn_keyboard_key_popup_action_lxx_dark</item>
         <item name="divider">@null</item>
+        <item name="keyLabelFlags">keepBackgroundAspectRatio</item>
     </style>
     <style
         name="SuggestionStripView.LXX_Dark"
diff --git a/java/res/values/themes-lxx-light.xml b/java/res/values/themes-lxx-light.xml
index 5cd8417..f607807 100644
--- a/java/res/values/themes-lxx-light.xml
+++ b/java/res/values/themes-lxx-light.xml
@@ -114,6 +114,7 @@
         <item name="android:background">@android:color/transparent</item>
         <item name="keyBackground">@drawable/btn_keyboard_key_popup_action_lxx_light</item>
         <item name="divider">@null</item>
+        <item name="keyLabelFlags">keepBackgroundAspectRatio</item>
     </style>
     <style
         name="SuggestionStripView.LXX_Light"
diff --git a/java/res/xml-sw600dp/key_styles_enter.xml b/java/res/xml-sw600dp/key_styles_enter.xml
index d066d2d..63ef2f8 100644
--- a/java/res/xml-sw600dp/key_styles_enter.xml
+++ b/java/res/xml-sw600dp/key_styles_enter.xml
@@ -80,13 +80,27 @@
         </default>
     </switch>
     <!-- Enter key style -->
-    <key-style
-        latin:styleName="defaultEnterKeyStyle"
-        latin:keySpec="!icon/enter_key|!code/key_enter"
-        latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor"
-        latin:keyActionFlags="noKeyPreview"
-        latin:backgroundType="action"
-        latin:parentStyle="navigateMoreKeysStyle" />
+    <switch>
+        <case latin:keyboardTheme="ICS|KLP">
+            <key-style
+                latin:styleName="defaultEnterKeyStyle"
+                latin:keySpec="!icon/enter_key|!code/key_enter"
+                latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="action"
+                latin:parentStyle="navigateMoreKeysStyle" />
+        </case>
+        <!-- keyboardTheme="LXXLight|LXXDark" -->
+        <default>
+            <key-style
+                latin:styleName="defaultEnterKeyStyle"
+                latin:keySpec="!icon/enter_key|!code/key_enter"
+                latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor|keepBackgroundAspectRatio"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="action"
+                latin:parentStyle="navigateMoreKeysStyle" />
+        </default>
+    </switch>
     <include latin:keyboardLayout="@xml/key_styles_actions" />
     <switch>
         <!-- Shift + Enter in textMultiLine field. -->
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 43ee26b..b36ddf2 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -80,11 +80,24 @@
         latin:keyActionFlags="isRepeatable|noKeyPreview"
         latin:backgroundType="functional" />
     <!-- emojiKeyStyle must be defined before including @xml/key_syles_enter. -->
-    <key-style
-        latin:styleName="emojiKeyStyle"
-        latin:keySpec="!icon/emoji_action_key|!code/key_emoji"
-        latin:keyActionFlags="noKeyPreview"
-        latin:backgroundType="action" />
+    <switch>
+        <case latin:keyboardTheme="ICS|KLP">
+            <key-style
+                latin:styleName="emojiKeyStyle"
+                latin:keySpec="!icon/emoji_action_key|!code/key_emoji"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="action" />
+        </case>
+        <!-- keyboardTheme="LXXLight|LXXDark" -->
+        <default>
+            <key-style
+                latin:styleName="emojiKeyStyle"
+                latin:keySpec="!icon/emoji_action_key|!code/key_emoji"
+                latin:keyLabelFlags="keepBackgroundAspectRatio"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="action" />
+        </default>
+    </switch>
     <include
         latin:keyboardLayout="@xml/key_styles_enter" />
     <!-- TODO: Currently there is no way to specify icon alignment per theme. -->
diff --git a/java/res/xml/key_styles_enter.xml b/java/res/xml/key_styles_enter.xml
index d6d01b8..564f465 100644
--- a/java/res/xml/key_styles_enter.xml
+++ b/java/res/xml/key_styles_enter.xml
@@ -212,13 +212,27 @@
         </default>
     </switch>
     <!-- Enter key style -->
-    <key-style
-        latin:styleName="defaultEnterKeyStyle"
-        latin:keySpec="!icon/enter_key|!code/key_enter"
-        latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor"
-        latin:keyActionFlags="noKeyPreview"
-        latin:backgroundType="action"
-        latin:parentStyle="navigateMoreKeysStyle" />
+    <switch>
+        <case latin:keyboardTheme="ICS|KLP">
+            <key-style
+                latin:styleName="defaultEnterKeyStyle"
+                latin:keySpec="!icon/enter_key|!code/key_enter"
+                latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="action"
+                latin:parentStyle="navigateMoreKeysStyle" />
+        </case>
+        <!-- keyboardTheme="LXXLight|LXXDark" -->
+        <default>
+            <key-style
+                latin:styleName="defaultEnterKeyStyle"
+                latin:keySpec="!icon/enter_key|!code/key_enter"
+                latin:keyLabelFlags="preserveCase|autoXScale|followKeyLabelRatio|followFunctionalTextColor|keepBackgroundAspectRatio"
+                latin:keyActionFlags="noKeyPreview"
+                latin:backgroundType="action"
+                latin:parentStyle="navigateMoreKeysStyle" />
+        </default>
+    </switch>
     <include latin:keyboardLayout="@xml/key_styles_actions" />
     <switch>
         <!-- Shift + Enter in textMultiLine field. -->
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index a6f9f3c..bd1c147 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -87,6 +87,7 @@
     private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
     private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
     private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000;
+    private static final int LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO = 0x100000;
     private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
     private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
 
@@ -697,6 +698,10 @@
         return (mLabelFlags & LABEL_FLAGS_AUTO_SCALE) == LABEL_FLAGS_AUTO_SCALE;
     }
 
+    public final boolean needsToKeepBackgroundAspectRatio(final int defaultFlags) {
+        return ((mLabelFlags | defaultFlags) & LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO) != 0;
+    }
+
     private final boolean isShiftedLetterActivated() {
         return (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
                 && !TextUtils.isEmpty(mHintLabel);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 075cd90..bb3cbb0 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -340,11 +340,25 @@
     // Draw key background.
     protected void onDrawKeyBackground(final Key key, final Canvas canvas,
             final Drawable background) {
-        final Rect padding = mKeyBackgroundPadding;
-        final int bgWidth = key.getDrawWidth() + padding.left + padding.right;
-        final int bgHeight = key.getHeight() + padding.top + padding.bottom;
-        final int bgX = -padding.left;
-        final int bgY = -padding.top;
+        final int keyWidth = key.getDrawWidth();
+        final int keyHeight = key.getHeight();
+        final int bgWidth, bgHeight, bgX, bgY;
+        if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags)) {
+            final int intrinsicWidth = background.getIntrinsicWidth();
+            final int intrinsicHeight = background.getIntrinsicHeight();
+            final float minScale = Math.min(
+                    keyWidth / (float)intrinsicWidth, keyHeight / (float)intrinsicHeight);
+            bgWidth = (int)(intrinsicWidth * minScale);
+            bgHeight = (int)(intrinsicHeight * minScale);
+            bgX = (keyWidth - bgWidth) / 2;
+            bgY = (keyHeight - bgHeight) / 2;
+        } else {
+            final Rect padding = mKeyBackgroundPadding;
+            bgWidth = keyWidth + padding.left + padding.right;
+            bgHeight = keyHeight + padding.top + padding.bottom;
+            bgX = -padding.left;
+            bgY = -padding.top;
+        }
         final Rect bounds = background.getBounds();
         if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
             background.setBounds(0, 0, bgWidth, bgHeight);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 73c84cd..abcfff8 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -308,8 +308,8 @@
                 dividerWidth = 0;
             }
             final MoreKeySpec[] moreKeys = key.getMoreKeys();
-            mParams.setParameters(moreKeys.length, key.getMoreKeysColumnNumber(), keyWidth, rowHeight,
-                    key.getX() + key.getWidth() / 2, keyboard.mId.mWidth,
+            mParams.setParameters(moreKeys.length, key.getMoreKeysColumnNumber(), keyWidth,
+                    rowHeight, key.getX() + key.getWidth() / 2, keyboard.mId.mWidth,
                     key.isMoreKeysFixedColumn(), key.isMoreKeysFixedOrder(), dividerWidth);
         }
 
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index fde94da..47aaead 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -60,7 +60,6 @@
     // HACK: This threshold is being used when adding a capitalized entry in the User History
     // dictionary.
     private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
-    private static final int MAX_DICTIONARY_FACILITATOR_CACHE_SIZE = 3;
 
     private DictionaryGroup mDictionaryGroup = new DictionaryGroup();
     private boolean mIsUserDictEnabled = false;
@@ -68,7 +67,6 @@
     // To synchronize assigning mDictionaryGroup to ensure closing dictionaries.
     private final Object mLock = new Object();
     private final DistracterFilter mDistracterFilter;
-    private final DictionaryFacilitatorLruCache mFacilitatorCacheForPersonalization;
 
     private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
             new String[] {
@@ -176,14 +174,10 @@
 
     public DictionaryFacilitator() {
         mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
-        mFacilitatorCacheForPersonalization = null;
     }
 
     public DictionaryFacilitator(final Context context) {
-        mFacilitatorCacheForPersonalization = new DictionaryFacilitatorLruCache(context,
-                MAX_DICTIONARY_FACILITATOR_CACHE_SIZE, "" /* dictionaryNamePrefix */);
-        mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context,
-                mFacilitatorCacheForPersonalization);
+        mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context);
     }
 
     public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
@@ -358,9 +352,6 @@
         for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
             dictionaryGroup.closeDict(dictType);
         }
-        if (mFacilitatorCacheForPersonalization != null) {
-            mFacilitatorCacheForPersonalization.evictAll();
-        }
         mDistracterFilter.close();
     }
 
@@ -609,14 +600,11 @@
         }
         // TODO: Get locale from personalizationDataChunk.mDetectedLanguage.
         final Locale dataChunkLocale = getLocale();
-        final DictionaryFacilitator dictionaryFacilitatorForLocale =
-                mFacilitatorCacheForPersonalization.get(dataChunkLocale);
         final ArrayList<LanguageModelParam> languageModelParams =
                 LanguageModelParam.createLanguageModelParamsFrom(
                         personalizationDataChunk.mTokens,
-                        personalizationDataChunk.mTimestampInSeconds,
-                        dictionaryFacilitatorForLocale, spacingAndPunctuations,
-                        new DistracterFilterCheckingIsInDictionary(
+                        personalizationDataChunk.mTimestampInSeconds, spacingAndPunctuations,
+                        dataChunkLocale, new DistracterFilterCheckingIsInDictionary(
                                 mDistracterFilter, personalizationDict));
         if (languageModelParams == null || languageModelParams.isEmpty()) {
             if (callback != null) {
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 787e4a5..94c6242 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -36,10 +36,38 @@
     public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
             final String testedWord, final Locale locale);
 
+    public int getWordHandlingType(final PrevWordsInfo prevWordsInfo, final String testedWord,
+            final Locale locale);
+
     public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
 
     public void close();
 
+    public static final class HandlingType {
+        private final static int REQUIRE_NO_SPECIAL_HANDLINGS = 0x0;
+        private final static int SHOULD_BE_LOWER_CASED = 0x1;
+        private final static int SHOULD_BE_HANDLED_AS_OOV = 0x2;
+
+        public static int getHandlingType(final boolean shouldBeLowerCased, final boolean isOov) {
+            int wordHandlingType = HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
+            if (shouldBeLowerCased) {
+                wordHandlingType |= HandlingType.SHOULD_BE_LOWER_CASED;
+            }
+            if (isOov) {
+                wordHandlingType |= HandlingType.SHOULD_BE_HANDLED_AS_OOV;
+            }
+            return wordHandlingType;
+        }
+
+        public static boolean shouldBeLowerCased(final int handlingType) {
+            return (handlingType & SHOULD_BE_LOWER_CASED) != 0;
+        }
+
+        public static boolean shouldBeHandledAsOov(final int handlingType) {
+            return (handlingType & SHOULD_BE_HANDLED_AS_OOV) != 0;
+        }
+    };
+
     public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
         @Override
         public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
@@ -48,6 +76,12 @@
         }
 
         @Override
+        public int getWordHandlingType(final PrevWordsInfo prevWordsInfo,
+                final String testedWord, final Locale locale) {
+            return HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
+        }
+
+        @Override
         public void close() {
         }
 
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index e10571e..1db5255 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -51,6 +51,7 @@
             DistracterFilterCheckingExactMatchesAndSuggestions.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    private static final int MAX_DICTIONARY_FACILITATOR_CACHE_SIZE = 3;
     private static final int MAX_DISTRACTERS_CACHE_SIZE = 1024;
 
     private final Context mContext;
@@ -73,15 +74,13 @@
      * Create a DistracterFilter instance.
      *
      * @param context the context.
-     * @param dictionaryFacilitatorLruCache the cache of dictionaryFacilitators that are used for
-     * checking distracters.
      */
-    public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context,
-            final DictionaryFacilitatorLruCache dictionaryFacilitatorLruCache) {
+    public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context) {
         mContext = context;
         mLocaleToSubtypeCache = new ConcurrentHashMap<>();
         mLocaleToKeyboardCache = new ConcurrentHashMap<>();
-        mDictionaryFacilitatorLruCache = dictionaryFacilitatorLruCache;
+        mDictionaryFacilitatorLruCache = new DictionaryFacilitatorLruCache(context,
+                MAX_DICTIONARY_FACILITATOR_CACHE_SIZE, "" /* dictionaryNamePrefix */);
         mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
     }
 
@@ -89,7 +88,8 @@
     public void close() {
         mLocaleToSubtypeCache.clear();
         mLocaleToKeyboardCache.clear();
-        mDistractersCache.evictAll();
+        mDictionaryFacilitatorLruCache.evictAll();
+        // Don't clear mDistractersCache.
     }
 
     @Override
@@ -194,9 +194,8 @@
             mDistractersCache.put(cacheKey, Boolean.TRUE);
             return true;
         }
-        final boolean isValidWord = dictionaryFacilitator.isValidWord(testedWord,
-                false /* ignoreCase */);
-        if (isValidWord) {
+        final boolean Word = dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */);
+        if (Word) {
             // Valid word is not a distractor.
             if (DEBUG) {
                 Log.d(TAG, "isDistracter: false (valid word)");
@@ -283,4 +282,41 @@
         }
         return false;
     }
+
+    private boolean shouldBeLowerCased(final PrevWordsInfo prevWordsInfo, final String testedWord,
+            final Locale locale) {
+        final DictionaryFacilitator dictionaryFacilitator =
+                mDictionaryFacilitatorLruCache.get(locale);
+        if (dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */)) {
+            return false;
+        }
+        final String lowerCaseTargetWord = testedWord.toLowerCase(locale);
+        if (testedWord.equals(lowerCaseTargetWord)) {
+            return false;
+        }
+        if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
+            return true;
+        }
+        if (StringUtils.getCapitalizationType(testedWord) == StringUtils.CAPITALIZE_FIRST
+                && !prevWordsInfo.isValid()) {
+            // TODO: Check beginning-of-sentence.
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public int getWordHandlingType(final PrevWordsInfo prevWordsInfo, final String testedWord,
+            final Locale locale) {
+        // TODO: Use this method for user history dictionary.
+        if (testedWord == null|| locale == null) {
+            return HandlingType.getHandlingType(false /* shouldBeLowerCased */, false /* isOov */);
+        }
+        final boolean shouldBeLowerCased = shouldBeLowerCased(prevWordsInfo, testedWord, locale);
+        final String caseModifiedWord =
+                shouldBeLowerCased ? testedWord.toLowerCase(locale) : testedWord;
+        final boolean isOov = !mDictionaryFacilitatorLruCache.get(locale).isValidWord(
+                caseModifiedWord, false /* ignoreCase */);
+        return HandlingType.getHandlingType(shouldBeLowerCased, isOov);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
index 4ad4ba7..349236f 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
@@ -48,6 +48,12 @@
     }
 
     @Override
+    public int getWordHandlingType(final PrevWordsInfo prevWordsInfo, final String testedWord,
+            final Locale locale) {
+        return mDistracterFilter.getWordHandlingType(prevWordsInfo, testedWord, locale);
+    }
+
+    @Override
     public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
         // Do nothing.
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index fbce3f2..05d1247 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.latin.DictionaryFacilitator;
 import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -81,8 +82,7 @@
     // Process a list of words and return a list of {@link LanguageModelParam} objects.
     public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom(
             final List<String> tokens, final int timestamp,
-            final DictionaryFacilitator dictionaryFacilitator,
-            final SpacingAndPunctuations spacingAndPunctuations,
+            final SpacingAndPunctuations spacingAndPunctuations, final Locale locale,
             final DistracterFilter distracterFilter) {
         final ArrayList<LanguageModelParam> languageModelParams = new ArrayList<>();
         final int N = tokens.size();
@@ -111,8 +111,7 @@
             }
             final LanguageModelParam languageModelParam =
                     detectWhetherVaildWordOrNotAndGetLanguageModelParam(
-                            prevWordsInfo, tempWord, timestamp, dictionaryFacilitator,
-                            distracterFilter);
+                            prevWordsInfo, tempWord, timestamp, locale, distracterFilter);
             if (languageModelParam == null) {
                 continue;
             }
@@ -125,47 +124,25 @@
 
     private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
             final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
-            final DictionaryFacilitator dictionaryFacilitator,
-            final DistracterFilter distracterFilter) {
-        final Locale locale = dictionaryFacilitator.getLocale();
+            final Locale locale, final DistracterFilter distracterFilter) {
         if (locale == null) {
             return null;
         }
-        if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
-            return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
-                    true /* isValidWord */, locale, distracterFilter);
+        final int wordHandlingType = distracterFilter.getWordHandlingType(prevWordsInfo,
+                targetWord, locale);
+        final String word = HandlingType.shouldBeLowerCased(wordHandlingType) ?
+                targetWord.toLowerCase(locale) : targetWord;
+        if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, targetWord, locale)) {
+            // The word is a distracter.
+            return null;
         }
-
-        final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
-        if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
-            // Add the lower-cased word.
-            return createAndGetLanguageModelParamOfWord(prevWordsInfo, lowerCaseTargetWord,
-                    timestamp, true /* isValidWord */, locale, distracterFilter);
-        }
-
-        // Treat the word as an OOV word.
-        return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
-                false /* isValidWord */, locale, distracterFilter);
+        return createAndGetLanguageModelParamOfWord(prevWordsInfo, word, timestamp,
+                !HandlingType.shouldBeHandledAsOov(wordHandlingType));
     }
 
     private static LanguageModelParam createAndGetLanguageModelParamOfWord(
-            final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
-            final boolean isValidWord, final Locale locale,
-            final DistracterFilter distracterFilter) {
-        final String word;
-        if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST
-                && !prevWordsInfo.isValid() && !isValidWord) {
-            word = targetWord.toLowerCase(locale);
-        } else {
-            word = targetWord;
-        }
-        // Check whether the word is a distracter to words in the dictionaries.
-        if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, word, locale)) {
-            if (DEBUG) {
-                Log.d(TAG, "The word (" + word + ") is a distracter. Skip this word.");
-            }
-            return null;
-        }
+            final PrevWordsInfo prevWordsInfo, final String word, final int timestamp,
+            final boolean isValidWord) {
         final int unigramProbability = isValidWord ?
                 UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD;
         if (!prevWordsInfo.isValid()) {
diff --git a/native/jni/NativeFileList.mk b/native/jni/NativeFileList.mk
index 68d2bbd..b896f38 100644
--- a/native/jni/NativeFileList.mk
+++ b/native/jni/NativeFileList.mk
@@ -131,6 +131,7 @@
     suggest/policyimpl/dictionary/structure/v4/content/terminal_position_lookup_table_test.cpp \
     suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer_test.cpp \
     suggest/policyimpl/dictionary/utils/byte_array_utils_test.cpp \
+    suggest/policyimpl/dictionary/utils/format_utils_test.cpp \
     suggest/policyimpl/dictionary/utils/sparse_table_test.cpp \
     suggest/policyimpl/dictionary/utils/trie_map_test.cpp \
     suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy_test.cpp \
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 2230dc7..32ff0ce 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -136,16 +136,15 @@
     }
 
     void initAsChild(const DicNode *const dicNode, const int childrenPtNodeArrayPos,
-            const int probability, const int wordId, const bool hasChildren,
-            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
-            const int *const mergedNodeCodePoints) {
+            const int unigramProbability, const int wordId, const bool isBlacklistedOrNotAWord,
+            const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
         uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         const uint16_t newLeavingDepth = static_cast<uint16_t>(
                 dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount);
         mDicNodeProperties.init(childrenPtNodeArrayPos, mergedNodeCodePoints[0],
-                probability, wordId, hasChildren, isBlacklistedOrNotAWord, newDepth,
-                newLeavingDepth, dicNode->mDicNodeProperties.getPrevWordIds());
+                unigramProbability, wordId, isBlacklistedOrNotAWord, newDepth, newLeavingDepth,
+                dicNode->mDicNodeProperties.getPrevWordIds());
         mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount,
                 mergedNodeCodePoints);
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
@@ -218,8 +217,9 @@
         return mDicNodeProperties.getChildrenPtNodeArrayPos();
     }
 
-    int getProbability() const {
-        return mDicNodeProperties.getProbability();
+    // TODO: Remove
+    int getUnigramProbability() const {
+        return mDicNodeProperties.getUnigramProbability();
     }
 
     AK_FORCE_INLINE bool isTerminalDicNode() const {
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index 87d2452..9f03e30 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -84,7 +84,7 @@
 /* static */ int DicNodeUtils::getBigramNodeProbability(
         const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
         const DicNode *const dicNode, MultiBigramMap *const multiBigramMap) {
-    const int unigramProbability = dicNode->getProbability();
+    const int unigramProbability = dicNode->getUnigramProbability();
     if (multiBigramMap) {
         const int *const prevWordIds = dicNode->getPrevWordIds();
         return multiBigramMap->getBigramProbability(dictionaryStructurePolicy,
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_vector.h b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
index b6a1951..dfeb3fc 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_vector.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
@@ -59,14 +59,12 @@
     }
 
     void pushLeavingChild(const DicNode *const dicNode, const int childrenPtNodeArrayPos,
-            const int probability, const int wordId, const bool hasChildren,
-            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
-            const int *const mergedNodeCodePoints) {
+            const int unigramProbability, const int wordId, const bool isBlacklistedOrNotAWord,
+            const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
         ASSERT(!mLock);
         mDicNodes.emplace_back();
-        mDicNodes.back().initAsChild(dicNode, childrenPtNodeArrayPos, probability,
-                wordId, hasChildren, isBlacklistedOrNotAWord, mergedNodeCodePointCount,
-                mergedNodeCodePoints);
+        mDicNodes.back().initAsChild(dicNode, childrenPtNodeArrayPos, unigramProbability,
+                wordId, isBlacklistedOrNotAWord, mergedNodeCodePointCount, mergedNodeCodePoints);
     }
 
     DicNode *operator[](const int id) {
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
index be3134c..6a8377a 100644
--- a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
@@ -29,22 +29,20 @@
 class DicNodeProperties {
  public:
     AK_FORCE_INLINE DicNodeProperties()
-            : mChildrenPtNodeArrayPos(NOT_A_DICT_POS), mProbability(NOT_A_PROBABILITY),
+            : mChildrenPtNodeArrayPos(NOT_A_DICT_POS), mUnigramProbability(NOT_A_PROBABILITY),
               mDicNodeCodePoint(NOT_A_CODE_POINT), mWordId(NOT_A_WORD_ID),
-              mHasChildrenPtNodes(false), mIsBlacklistedOrNotAWord(false), mDepth(0),
-              mLeavingDepth(0) {}
+              mIsBlacklistedOrNotAWord(false), mDepth(0), mLeavingDepth(0) {}
 
     ~DicNodeProperties() {}
 
     // Should be called only once per DicNode is initialized.
-    void init(const int childrenPos, const int nodeCodePoint, const int probability,
-            const int wordId, const bool hasChildren, const bool isBlacklistedOrNotAWord,
-            const uint16_t depth, const uint16_t leavingDepth, const int *const prevWordIds) {
+    void init(const int childrenPos, const int nodeCodePoint, const int unigramProbability,
+            const int wordId, const bool isBlacklistedOrNotAWord, const uint16_t depth,
+            const uint16_t leavingDepth, const int *const prevWordIds) {
         mChildrenPtNodeArrayPos = childrenPos;
         mDicNodeCodePoint = nodeCodePoint;
-        mProbability = probability;
+        mUnigramProbability = unigramProbability;
         mWordId = wordId;
-        mHasChildrenPtNodes = hasChildren;
         mIsBlacklistedOrNotAWord = isBlacklistedOrNotAWord;
         mDepth = depth;
         mLeavingDepth = leavingDepth;
@@ -55,9 +53,8 @@
     void init(const int rootPtNodeArrayPos, const int *const prevWordIds) {
         mChildrenPtNodeArrayPos = rootPtNodeArrayPos;
         mDicNodeCodePoint = NOT_A_CODE_POINT;
-        mProbability = NOT_A_PROBABILITY;
+        mUnigramProbability = NOT_A_PROBABILITY;
         mWordId = NOT_A_WORD_ID;
-        mHasChildrenPtNodes = true;
         mIsBlacklistedOrNotAWord = false;
         mDepth = 0;
         mLeavingDepth = 0;
@@ -67,9 +64,8 @@
     void initByCopy(const DicNodeProperties *const dicNodeProp) {
         mChildrenPtNodeArrayPos = dicNodeProp->mChildrenPtNodeArrayPos;
         mDicNodeCodePoint = dicNodeProp->mDicNodeCodePoint;
-        mProbability = dicNodeProp->mProbability;
+        mUnigramProbability = dicNodeProp->mUnigramProbability;
         mWordId = dicNodeProp->mWordId;
-        mHasChildrenPtNodes = dicNodeProp->mHasChildrenPtNodes;
         mIsBlacklistedOrNotAWord = dicNodeProp->mIsBlacklistedOrNotAWord;
         mDepth = dicNodeProp->mDepth;
         mLeavingDepth = dicNodeProp->mLeavingDepth;
@@ -80,9 +76,8 @@
     void init(const DicNodeProperties *const dicNodeProp, const int codePoint) {
         mChildrenPtNodeArrayPos = dicNodeProp->mChildrenPtNodeArrayPos;
         mDicNodeCodePoint = codePoint; // Overwrite the node char of a passing child
-        mProbability = dicNodeProp->mProbability;
+        mUnigramProbability = dicNodeProp->mUnigramProbability;
         mWordId = dicNodeProp->mWordId;
-        mHasChildrenPtNodes = dicNodeProp->mHasChildrenPtNodes;
         mIsBlacklistedOrNotAWord = dicNodeProp->mIsBlacklistedOrNotAWord;
         mDepth = dicNodeProp->mDepth + 1; // Increment the depth of a passing child
         mLeavingDepth = dicNodeProp->mLeavingDepth;
@@ -93,8 +88,8 @@
         return mChildrenPtNodeArrayPos;
     }
 
-    int getProbability() const {
-        return mProbability;
+    int getUnigramProbability() const {
+        return mUnigramProbability;
     }
 
     int getDicNodeCodePoint() const {
@@ -115,7 +110,7 @@
     }
 
     bool hasChildren() const {
-        return mHasChildrenPtNodes || mDepth != mLeavingDepth;
+        return (mChildrenPtNodeArrayPos != NOT_A_DICT_POS) || mDepth != mLeavingDepth;
     }
 
     bool isBlacklistedOrNotAWord() const {
@@ -135,10 +130,11 @@
     // Use a default copy constructor and an assign operator because shallow copies are ok
     // for this class
     int mChildrenPtNodeArrayPos;
-    int mProbability;
+    // TODO: Remove
+    int mUnigramProbability;
     int mDicNodeCodePoint;
     int mWordId;
-    bool mHasChildrenPtNodes;
+    // TODO: Remove
     bool mIsBlacklistedOrNotAWord;
     uint16_t mDepth;
     uint16_t mLeavingDepth;
diff --git a/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp b/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp
index b372b6b..94d7c88 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary_utils.cpp
@@ -54,15 +54,15 @@
         current.swap(next);
     }
 
-    int maxProbability = NOT_A_PROBABILITY;
+    int maxUnigramProbability = NOT_A_PROBABILITY;
     for (const DicNode &dicNode : current) {
         if (!dicNode.isTerminalDicNode()) {
             continue;
         }
         // dicNode can contain case errors, accent errors, intentional omissions or digraphs.
-        maxProbability = std::max(maxProbability, dicNode.getProbability());
+        maxUnigramProbability = std::max(maxUnigramProbability, dicNode.getUnigramProbability());
     }
-    return maxProbability;
+    return maxUnigramProbability;
 }
 
 /* static */ void DictionaryUtils::processChildDicNodes(
diff --git a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
index ad860c4..cecb4e2 100644
--- a/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
+++ b/native/jni/src/suggest/core/result/suggestions_output_utils.cpp
@@ -87,7 +87,7 @@
             + doubleLetterCost;
     const bool isPossiblyOffensiveWord =
             traverseSession->getDictionaryStructurePolicy()->getProbability(
-                    terminalDicNode->getProbability(), NOT_A_PROBABILITY) <= 0;
+                    terminalDicNode->getUnigramProbability(), NOT_A_PROBABILITY) <= 0;
     const bool isExactMatch =
             ErrorTypeUtils::isExactMatch(terminalDicNode->getContainedErrorTypes());
     const bool isExactMatchWithIntentionalOmission =
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 f901331..6480374 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
@@ -78,7 +78,7 @@
         }
         const int wordId = isTerminal ? ptNodeParams.getHeadPos() : NOT_A_WORD_ID;
         childDicNodes->pushLeavingChild(dicNode, ptNodeParams.getChildrenPos(),
-                ptNodeParams.getProbability(), wordId, ptNodeParams.hasChildren(),
+                ptNodeParams.getProbability(), wordId,
                 ptNodeParams.isBlacklisted()
                         || ptNodeParams.isNotAWord() /* isBlacklistedOrNotAWord */,
                 ptNodeParams.getCodePointCount(), ptNodeParams.getCodePoints());
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
index b36c6f4..e0406ab 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
@@ -378,7 +378,6 @@
     if (CharUtils::isInUnicodeSpace(mergedNodeCodePoints[0])) {
         const int wordId = PatriciaTrieReadingUtils::isTerminal(flags) ? ptNodePos : NOT_A_WORD_ID;
         childDicNodes->pushLeavingChild(dicNode, childrenPos, probability, wordId,
-                PatriciaTrieReadingUtils::hasChildrenInFlags(flags),
                 PatriciaTrieReadingUtils::isBlacklisted(flags)
                         || PatriciaTrieReadingUtils::isNotAWord(flags),
                 mergedNodeCodePointCount, mergedNodeCodePoints);
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 aca2f6c..466c499 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
@@ -68,7 +68,7 @@
         }
         const int wordId = isTerminal ? ptNodeParams.getTerminalId() : NOT_A_WORD_ID;
         childDicNodes->pushLeavingChild(dicNode, ptNodeParams.getChildrenPos(),
-                ptNodeParams.getProbability(), wordId, ptNodeParams.hasChildren(),
+                ptNodeParams.getProbability(), wordId,
                 ptNodeParams.isBlacklisted()
                         || ptNodeParams.isNotAWord() /* isBlacklistedOrNotAWord */,
                 ptNodeParams.getCodePointCount(), ptNodeParams.getCodePoints());
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
index cb3dfac..ed9df8e 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -162,7 +162,8 @@
     }
 
     AK_FORCE_INLINE bool isGoodToTraverseNextWord(const DicNode *const dicNode) const {
-        const int probability = dicNode->getProbability();
+        // TODO: Quit using unigram probability and use probability in the context.
+        const int probability = dicNode->getUnigramProbability();
         if (probability < ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY) {
             return false;
         }
diff --git a/native/jni/tests/suggest/policyimpl/dictionary/utils/format_utils_test.cpp b/native/jni/tests/suggest/policyimpl/dictionary/utils/format_utils_test.cpp
new file mode 100644
index 0000000..15f560c
--- /dev/null
+++ b/native/jni/tests/suggest/policyimpl/dictionary/utils/format_utils_test.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include "utils/byte_array_view.h"
+
+namespace latinime {
+namespace {
+
+TEST(FormatUtilsTest, TestMagicNumber) {
+    EXPECT_EQ(0x9BC13AFE, FormatUtils::MAGIC_NUMBER) << "Magic number must not be changed.";
+}
+
+const std::vector<uint8_t> getBuffer(const int magicNumber, const int version, const uint16_t flags,
+        const size_t headerSize) {
+    std::vector<uint8_t> buffer;
+    buffer.push_back(magicNumber >> 24);
+    buffer.push_back(magicNumber >> 16);
+    buffer.push_back(magicNumber >> 8);
+    buffer.push_back(magicNumber);
+
+    buffer.push_back(version >> 8);
+    buffer.push_back(version);
+
+    buffer.push_back(flags >> 8);
+    buffer.push_back(flags);
+
+    buffer.push_back(headerSize >> 24);
+    buffer.push_back(headerSize >> 16);
+    buffer.push_back(headerSize >> 8);
+    buffer.push_back(headerSize);
+    return buffer;
+}
+
+TEST(FormatUtilsTest, TestDetectFormatVersion) {
+    EXPECT_EQ(FormatUtils::UNKNOWN_VERSION,
+            FormatUtils::detectFormatVersion(ReadOnlyByteArrayView()));
+
+    {
+        const std::vector<uint8_t> buffer =
+                getBuffer(FormatUtils::MAGIC_NUMBER, FormatUtils::VERSION_2, 0, 0);
+        EXPECT_EQ(FormatUtils::VERSION_2, FormatUtils::detectFormatVersion(
+                ReadOnlyByteArrayView(buffer.data(), buffer.size())));
+    }
+    {
+        const std::vector<uint8_t> buffer =
+                getBuffer(FormatUtils::MAGIC_NUMBER, FormatUtils::VERSION_4, 0, 0);
+        EXPECT_EQ(FormatUtils::VERSION_4, FormatUtils::detectFormatVersion(
+                ReadOnlyByteArrayView(buffer.data(), buffer.size())));
+    }
+    {
+        const std::vector<uint8_t> buffer =
+                getBuffer(FormatUtils::MAGIC_NUMBER, FormatUtils::VERSION_4_DEV, 0, 0);
+        EXPECT_EQ(FormatUtils::VERSION_4_DEV, FormatUtils::detectFormatVersion(
+                ReadOnlyByteArrayView(buffer.data(), buffer.size())));
+    }
+
+    {
+        const std::vector<uint8_t> buffer =
+                getBuffer(FormatUtils::MAGIC_NUMBER - 1, FormatUtils::VERSION_2, 0, 0);
+        EXPECT_EQ(FormatUtils::UNKNOWN_VERSION, FormatUtils::detectFormatVersion(
+                ReadOnlyByteArrayView(buffer.data(), buffer.size())));
+    }
+    {
+        const std::vector<uint8_t> buffer =
+                getBuffer(FormatUtils::MAGIC_NUMBER, 100, 0, 0);
+        EXPECT_EQ(FormatUtils::UNKNOWN_VERSION, FormatUtils::detectFormatVersion(
+                ReadOnlyByteArrayView(buffer.data(), buffer.size())));
+    }
+    {
+        const std::vector<uint8_t> buffer =
+                getBuffer(FormatUtils::MAGIC_NUMBER, FormatUtils::VERSION_2, 0, 0);
+        EXPECT_EQ(FormatUtils::UNKNOWN_VERSION, FormatUtils::detectFormatVersion(
+                ReadOnlyByteArrayView(buffer.data(), buffer.size() - 1)));
+    }
+}
+
+}  // namespace
+}  // namespace latinime
diff --git a/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java b/tests/src/com/android/inputmethod/latin/utils/DistracterFilterTest.java
similarity index 87%
rename from tests/src/com/android/inputmethod/latin/DistracterFilterTest.java
rename to tests/src/com/android/inputmethod/latin/utils/DistracterFilterTest.java
index af22fb8..5fbd36a 100644
--- a/tests/src/com/android/inputmethod/latin/DistracterFilterTest.java
+++ b/tests/src/com/android/inputmethod/latin/utils/DistracterFilterTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -24,24 +24,22 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.view.inputmethod.InputMethodSubtype;
 
-import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatchesAndSuggestions;
+import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType;
 
 /**
  * Unit test for DistracterFilter
  */
 @LargeTest
 public class DistracterFilterTest extends AndroidTestCase {
-    private DictionaryFacilitatorLruCache mDictionaryFacilitatorLruCache;
     private DistracterFilterCheckingExactMatchesAndSuggestions mDistracterFilter;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         final Context context = getContext();
-        mDictionaryFacilitatorLruCache = new DictionaryFacilitatorLruCache(context,
-                2 /* maxSize */, "" /* dictionaryNamePrefix */);
-        mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context,
-                mDictionaryFacilitatorLruCache);
+        mDistracterFilter = new DistracterFilterCheckingExactMatchesAndSuggestions(context);
         RichInputMethodManager.init(context);
         final RichInputMethodManager richImm = RichInputMethodManager.getInstance();
         final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
@@ -56,7 +54,7 @@
 
     @Override
     protected void tearDown() {
-        mDictionaryFacilitatorLruCache.evictAll();
+        mDistracterFilter.close();
     }
 
     public void testIsDistractorToWordsInDictionaries() {
@@ -203,4 +201,25 @@
         assertTrue(mDistracterFilter.isDistracterToWordsInDictionaries(
                 EMPTY_PREV_WORDS_INFO, typedWord, localeFrFr));
     }
+
+    public void testGetWordHandlingType() {
+        final Locale localeEnUs = new Locale("en", "US");
+        final PrevWordsInfo EMPTY_PREV_WORDS_INFO = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+        int handlingType = 0;
+
+        handlingType = mDistracterFilter.getWordHandlingType(EMPTY_PREV_WORDS_INFO,
+                "this", localeEnUs);
+        assertFalse(HandlingType.shouldBeLowerCased(handlingType));
+        assertFalse(HandlingType.shouldBeHandledAsOov(handlingType));
+
+        handlingType = mDistracterFilter.getWordHandlingType(EMPTY_PREV_WORDS_INFO,
+                "This", localeEnUs);
+        assertTrue(HandlingType.shouldBeLowerCased(handlingType));
+        assertFalse(HandlingType.shouldBeHandledAsOov(handlingType));
+
+        handlingType = mDistracterFilter.getWordHandlingType(EMPTY_PREV_WORDS_INFO,
+                "thibk", localeEnUs);
+        assertFalse(HandlingType.shouldBeLowerCased(handlingType));
+        assertTrue(HandlingType.shouldBeHandledAsOov(handlingType));
+    }
 }