Merge "Record reading error during traversing dictionaries."
diff --git a/java/res/values/config-common.xml b/java/res/values/config-common.xml
index 224aabd..20d5860 100644
--- a/java/res/values/config-common.xml
+++ b/java/res/values/config-common.xml
@@ -35,10 +35,10 @@
 
     <integer name="config_ignore_alt_code_key_timeout">350</integer>
 
-    <integer name="config_key_preview_show_up_duration">0</integer>
-    <integer name="config_key_preview_dismiss_duration">70</integer>
-    <fraction name="config_key_preview_show_up_start_scale">100%</fraction>
-    <fraction name="config_key_preview_dismiss_end_scale">100%</fraction>
+    <integer name="config_key_preview_show_up_duration">17</integer>
+    <integer name="config_key_preview_dismiss_duration">53</integer>
+    <fraction name="config_key_preview_show_up_start_scale">98%</fraction>
+    <fraction name="config_key_preview_dismiss_end_scale">94%</fraction>
     <!-- TODO: consolidate key preview linger timeout with the above animation parameters. -->
     <integer name="config_key_preview_linger_timeout">70</integer>
     <!-- Suppress showing key preview duration after batch input in millisecond -->
diff --git a/java/res/values/strings-config-important-notice.xml b/java/res/values/strings-config-important-notice.xml
index 6a9fe28..3be95d3 100644
--- a/java/res/values/strings-config-important-notice.xml
+++ b/java/res/values/strings-config-important-notice.xml
@@ -20,7 +20,8 @@
 
 <resources>
     <integer name="config_important_notice_version">0</integer>
-    <!-- The title of the important notice displayed on the suggestion strip. -->
+    <!-- TODO: Make title and contents resource to string array indexed by version. -->
+    <!-- The text of the important notice displayed on the suggestion strip. -->
     <string name="important_notice_title"></string>
     <!-- The contents of the important notice. -->
     <string name="important_notice_contents"></string>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index c9be449..a39ce4a 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -90,11 +90,11 @@
             android:entries="@array/prefs_suggestion_visibilities"
             android:defaultValue="@string/prefs_suggestion_visibility_default_value" />
         <CheckBoxPreference
-            android:key="pref_use_personalized_dicts"
+            android:key="pref_key_use_personalized_dicts"
             android:title="@string/use_personalized_dicts"
             android:summary="@string/use_personalized_dicts_summary"
             android:persistent="true"
-            android:defaultValue="false" />
+            android:defaultValue="true" />
     </PreferenceCategory>
     <PreferenceCategory
         android:title="@string/gesture_typing_category"
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 11a9d1f..ae9bdf3f 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -89,8 +89,6 @@
     }
 
     private synchronized void registerObserver(final Context context) {
-        // Perform a managed query. The Activity will handle closing and requerying the cursor
-        // when needed.
         if (mObserver != null) return;
         ContentResolver cres = context.getContentResolver();
         cres.registerContentObserver(Contacts.CONTENT_URI, true, mObserver =
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 3b9be43..230739d 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -62,7 +62,7 @@
     private static final boolean DBG_STRESS_TEST = false;
 
     private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
-    private static final int TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS = 1000;
+    private static final int TIMEOUT_FOR_READ_OPS_FOR_TESTS_IN_MILLISECONDS = 10000;
 
     /**
      * The maximum length of a word in this dictionary.
@@ -750,7 +750,7 @@
     @UsedForTesting
     public boolean isInUnderlyingBinaryDictionaryForTests(final String word) {
         final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
-        getExecutor(mDictName).executePrioritized(new Runnable() {
+        getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
                 if (mDictType == Dictionary.TYPE_USER_HISTORY) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5d90e10..346ba85 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -541,7 +541,7 @@
             shouldKeepUserHistoryDictionaries = true;
             // TODO: Eliminate this restriction
             shouldKeepPersonalizationDictionaries =
-                    mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypes();
+                    mSubtypeSwitcher.isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes();
         } else {
             shouldKeepUserHistoryDictionaries = false;
             shouldKeepPersonalizationDictionaries = false;
@@ -1183,14 +1183,10 @@
     @Override
     public void showImportantNoticeContents() {
         final Context context = this;
-        final OnShowListener onShowListener = new OnShowListener() {
-            @Override
-            public void onShow(final DialogInterface dialog) {
-                ImportantNoticeUtils.updateLastImportantNoticeVersion(context);
-                onShowImportantNoticeDialog(
-                        ImportantNoticeUtils.getCurrentImportantNoticeVersion(context));
-            }
-        };
+        final AlertDialog.Builder builder =
+                new AlertDialog.Builder(context, AlertDialog.THEME_HOLO_DARK);
+        builder.setMessage(ImportantNoticeUtils.getNextImportantNoticeContents(context));
+        builder.setPositiveButton(android.R.string.ok, null /* listener */);
         final OnClickListener onClickListener = new OnClickListener() {
             @Override
             public void onClick(final DialogInterface dialog, final int position) {
@@ -1199,34 +1195,23 @@
                 }
             }
         };
-        final OnDismissListener onDismissListener = new OnDismissListener() {
+        builder.setNegativeButton(R.string.go_to_settings, onClickListener);
+        final AlertDialog importantNoticeDialog = builder.create();
+        importantNoticeDialog.setOnShowListener(new OnShowListener() {
             @Override
-            public void onDismiss(DialogInterface dialog) {
+            public void onShow(final DialogInterface dialog) {
+                ImportantNoticeUtils.updateLastImportantNoticeVersion(context);
+            }
+        });
+        importantNoticeDialog.setOnDismissListener(new OnDismissListener() {
+            @Override
+            public void onDismiss(final DialogInterface dialog) {
                 setNeutralSuggestionStrip();
             }
-        };
-        final String importantNoticeContents = ImportantNoticeUtils.getImportantNoticeContents(
-                context);
-        final AlertDialog.Builder builder = new AlertDialog.Builder(
-                context, AlertDialog.THEME_HOLO_DARK);
-        builder.setMessage(importantNoticeContents)
-                .setPositiveButton(android.R.string.ok, null /* listener */)
-                .setNegativeButton(R.string.go_to_settings, onClickListener);
-        final AlertDialog importantNoticeDialog = builder.create();
-        importantNoticeDialog.setOnShowListener(onShowListener);
-        importantNoticeDialog.setOnDismissListener(onDismissListener);
+        });
         showOptionDialog(importantNoticeDialog);
     }
 
-    private void onShowImportantNoticeDialog(final int importantNoticeVersion) {
-        if (importantNoticeVersion ==
-                ImportantNoticeUtils.VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS) {
-            mSettings.writeUsePersonalizationDictionary(true /* enabled */);
-            loadSettings();
-            initSuggest();
-        }
-    }
-
     public void displaySettingsDialog() {
         if (isShowingOptionDialog()) {
             return;
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index ebad9bc..cc2db4c 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -810,11 +810,11 @@
         if (mExpectedSelStart == newSelStart && mExpectedSelEnd == newSelEnd) return true;
         // This update is not belated if mExpectedSelStart and mExpectedSelEnd match the old
         // values, and one of newSelStart or newSelEnd is updated to a different value. In this
-        // case, there is likely something other than the IME has moved the selection endpoint
+        // case, it is likely that something other than the IME has moved the selection endpoint
         // to the new value.
         if (mExpectedSelStart == oldSelStart && mExpectedSelEnd == oldSelEnd
                 && (oldSelStart != newSelStart || oldSelEnd != newSelEnd)) return false;
-        // If nether of the above two cases holds, then the system may be having trouble keeping up
+        // If neither of the above two cases hold, then the system may be having trouble keeping up
         // with updates. If 1) the selection is a cursor, 2) newSelStart is between oldSelStart
         // and mExpectedSelStart, and 3) newSelEnd is between oldSelEnd and mExpectedSelEnd, then
         // assume a belated update.
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 860575a..935dd96 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -37,9 +37,11 @@
 import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 
 public final class SubtypeSwitcher {
     private static boolean DBG = LatinImeLogger.sDBG;
@@ -273,12 +275,26 @@
         return mNeedsToDisplayLanguage.getValue();
     }
 
-    public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypes() {
+    public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
         final Locale systemLocale = mResources.getConfiguration().locale;
-        final List<InputMethodSubtype> enabledSubtypesOfThisIme =
-                mRichImm.getMyEnabledInputMethodSubtypeList(true);
-        for (final InputMethodSubtype subtype : enabledSubtypesOfThisIme) {
-            if (!systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
+        final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes =
+                new HashSet<InputMethodSubtype>();
+        final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager();
+        final List<InputMethodInfo> enabledInputMethodInfoList =
+                inputMethodManager.getEnabledInputMethodList();
+        for (final InputMethodInfo info : enabledInputMethodInfoList) {
+            final List<InputMethodSubtype> enabledSubtypes =
+                    inputMethodManager.getEnabledInputMethodSubtypeList(
+                            info, true /* allowsImplicitlySelectedSubtypes */);
+            if (enabledSubtypes.isEmpty()) {
+                // An IME with no subtypes is found.
+                return false;
+            }
+            enabledSubtypesOfEnabledImes.addAll(enabledSubtypes);
+        }
+        for (final InputMethodSubtype subtype : enabledSubtypesOfEnabledImes) {
+            if (!subtype.isAuxiliary() && !subtype.getLocale().isEmpty()
+                    && !systemLocale.equals(SubtypeLocaleUtils.getSubtypeLocale(subtype))) {
                 return false;
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 2a195f5..3e3cbf0 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -97,8 +97,6 @@
             mLocale = localeStr;
         }
         mAlsoUseMoreRestrictiveLocales = alsoUseMoreRestrictiveLocales;
-        // Perform a managed query. The Activity will handle closing and re-querying the cursor
-        // when needed.
         ContentResolver cres = context.getContentResolver();
 
         mObserver = new ContentObserver(null) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 3fc2cf8..52a6f5f 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -77,9 +77,10 @@
     public int mSpaceState;
     // Never null
     public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
-    public Suggest mSuggest;
+    // TODO: mSuggest should be touched by a single thread.
+    public volatile Suggest mSuggest;
     // The event interpreter should never be null.
-    public EventInterpreter mEventInterpreter;
+    public final EventInterpreter mEventInterpreter;
 
     public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
     public final WordComposer mWordComposer;
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
index 23aa05d..88fff38 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java
@@ -53,6 +53,10 @@
 
     @Override
     public DictionaryHeader readHeader() throws IOException, UnsupportedFormatException {
+        final DictionaryHeader header = mBinaryDictionary.getHeader();
+        if (header == null) {
+            throw new IOException("Cannot read the dictionary header.");
+        }
         return mBinaryDictionary.getHeader();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 6a1503f..b51c765 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -55,7 +55,7 @@
     public static final String PREF_MISC_SETTINGS = "misc_settings";
     public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
     public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
-    public static final String PREF_USE_PERSONALIZED_DICTS = "pref_use_personalized_dicts";
+    public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "pref_key_use_personalized_dicts";
     public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD =
             "pref_key_use_double_space_period";
     public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE =
@@ -421,10 +421,6 @@
         return mPrefs.getStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, emptySet);
     }
 
-    public void writeUsePersonalizationDictionary(final boolean enabled) {
-        mPrefs.edit().putBoolean(PREF_USE_PERSONALIZED_DICTS, enabled).apply();
-    }
-
     public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
         prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
     }
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 0f3deea..77968f7 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -128,7 +128,7 @@
                 Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
         mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs);
         mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
-        mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_USE_PERSONALIZED_DICTS, false);
+        mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true);
         mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true);
         mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res);
         mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 2966a8b..4ef562d 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -237,7 +237,7 @@
         if (width <= 0) {
             return false;
         }
-        final String importantNoticeTitle = ImportantNoticeUtils.getImportantNoticeTitle(
+        final String importantNoticeTitle = ImportantNoticeUtils.getNextImportantNoticeTitle(
                 getContext());
         if (TextUtils.isEmpty(importantNoticeTitle)) {
             return false;
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 2f41ce9..97a924d 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -53,8 +53,7 @@
     }
 
     public static TreeSet<String> getUserDictionaryLocalesSet(Activity activity) {
-        @SuppressWarnings("deprecation")
-        final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI,
+        final Cursor cursor = activity.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
                 new String[] { UserDictionary.Words.LOCALE },
                 null, null, null);
         final TreeSet<String> localeSet = new TreeSet<String>();
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index 220efb5..cf2014a 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -141,7 +141,10 @@
 
         mLocale = locale;
         // WARNING: The following cursor is never closed! TODO: don't put that in a member, and
-        // make sure all cursors are correctly closed.
+        // make sure all cursors are correctly closed. Also, this comes from a call to
+        // Activity#managedQuery, which has been deprecated for a long time (and which FORBIDS
+        // closing the cursor, so take care when resolving this TODO). We should either use a
+        // regular query and close the cursor, or switch to a LoaderManager and a CursorLoader.
         mCursor = createCursor(locale);
         TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
         emptyView.setText(R.string.user_dict_settings_empty_text);
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index dd418b8..6b0bb86 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -30,9 +30,9 @@
 
     // {@link SharedPreferences} name to save the last important notice version that has been
     // displayed to users.
-    private static final String PREFERENCE_NAME = "important_notice";
+    private static final String PREFERENCE_NAME = "important_notice_pref";
     private static final String KEY_IMPORTANT_NOTICE_VERSION = "important_notice_version";
-    public static final int VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS = 2;
+    public static final int VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS = 1;
 
     // Copy of the hidden {@link Settings.Secure#USER_SETUP_COMPLETE} settings key.
     // The value is zero until each multiuser completes system setup wizard.
@@ -64,9 +64,16 @@
         return context.getResources().getInteger(R.integer.config_important_notice_version);
     }
 
+    private static int getLastImportantNoticeVersion(final Context context) {
+        return getImportantNoticePreferences(context).getInt(KEY_IMPORTANT_NOTICE_VERSION, 0);
+    }
+
+    private static int getNextImportantNoticeVersion(final Context context) {
+        return getLastImportantNoticeVersion(context) + 1;
+    }
+
     private static boolean hasNewImportantNotice(final Context context) {
-        final SharedPreferences prefs = getImportantNoticePreferences(context);
-        final int lastVersion = prefs.getInt(KEY_IMPORTANT_NOTICE_VERSION, 0);
+        final int lastVersion = getLastImportantNoticeVersion(context);
         return getCurrentImportantNoticeVersion(context) > lastVersion;
     }
 
@@ -79,14 +86,15 @@
     }
 
     public static void updateLastImportantNoticeVersion(final Context context) {
-        final SharedPreferences prefs = getImportantNoticePreferences(context);
-        prefs.edit()
-                .putInt(KEY_IMPORTANT_NOTICE_VERSION, getCurrentImportantNoticeVersion(context))
+        getImportantNoticePreferences(context)
+                .edit()
+                .putInt(KEY_IMPORTANT_NOTICE_VERSION, getNextImportantNoticeVersion(context))
                 .apply();
     }
 
-    public static String getImportantNoticeTitle(final Context context) {
-        switch (getCurrentImportantNoticeVersion(context)) {
+    // TODO: Make title resource to string array indexed by version.
+    public static String getNextImportantNoticeTitle(final Context context) {
+        switch (getNextImportantNoticeVersion(context)) {
         case VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS:
             return context.getString(R.string.important_notice_title);
         default:
@@ -94,8 +102,9 @@
         }
     }
 
-    public static String getImportantNoticeContents(final Context context) {
-        switch (getCurrentImportantNoticeVersion(context)) {
+    // TODO: Make content resource to string array indexed by version.
+    public static String getNextImportantNoticeContents(final Context context) {
+        switch (getNextImportantNoticeVersion(context)) {
         case VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS:
             return context.getString(R.string.important_notice_contents);
         default:
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index dff5a77..449030c 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -20,6 +20,7 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
@@ -44,6 +45,43 @@
         "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
     };
 
+    private int mCurrentTime = 0;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mCurrentTime = 0;
+        setCurrentTimeForTestMode(mCurrentTime);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        stopTestModeInNativeCode();
+        super.tearDown();
+    }
+
+    private void forcePassingShortTime() {
+        // 4 days.
+        final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(4);
+        mCurrentTime += timeToElapse;
+        setCurrentTimeForTestMode(mCurrentTime);
+    }
+
+    private void forcePassingLongTime() {
+        // 60 days.
+        final int timeToElapse = (int)TimeUnit.DAYS.toSeconds(60);
+        mCurrentTime += timeToElapse;
+        setCurrentTimeForTestMode(mCurrentTime);
+    }
+
+    private static int setCurrentTimeForTestMode(final int currentTime) {
+        return BinaryDictionary.setCurrentTimeForTest(currentTime);
+    }
+
+    private static int stopTestModeInNativeCode() {
+        return BinaryDictionary.setCurrentTimeForTest(-1);
+    }
+
     /**
      * Generates a random word.
      */
@@ -207,4 +245,28 @@
             FileUtils.deleteRecursively(dictFile);
         }
     }
+
+    public void testDecaying() {
+        final Locale dummyLocale = new Locale("test_decaying" + System.currentTimeMillis());
+        final int numberOfWords = 5000;
+        final Random random = new Random(123456);
+        clearHistory(dummyLocale);
+        final List<String> words = generateWords(numberOfWords, random);
+        final UserHistoryDictionary dict =
+                PersonalizationHelper.getUserHistoryDictionary(getContext(), dummyLocale);
+        String prevWord = null;
+        for (final String word : words) {
+            dict.addToDictionary(prevWord, word, true, mCurrentTime);
+            prevWord = word;
+            assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word));
+        }
+        forcePassingShortTime();
+        for (final String word : words) {
+            assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word));
+        }
+        forcePassingLongTime();
+        for (final String word : words) {
+            assertFalse(dict.isInUnderlyingBinaryDictionaryForTests(word));
+        }
+    }
 }