Merge "Add BatchInputArbiter"
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index f4d072d..f618473 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -484,8 +484,6 @@
     <string name="prefs_read_external_dictionary">Read external dictionary file</string>
     <!-- Title of the settings for using only personalization dictionary -->
     <string name="prefs_use_only_personalization_dictionary" translatable="false">Use only personalization dictionary</string>
-    <!-- Title of the settings for boosting personalization dictionary -->
-    <string name="prefs_boost_personalization_dictionary" translatable="false">Boost personalization dictionary</string>
     <!-- Message to show when there are no files to install as an external dictionary [CHAR LIMIT=100] -->
     <string name="read_external_dictionary_no_files_message">No dictionary files in the Downloads folder</string>
     <!-- Title of the dialog that selects a file to install as an external dictionary [CHAR LIMIT=50] -->
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 124c443..8d9508e 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -58,12 +58,6 @@
         android:persistent="true"
         android:title="@string/prefs_use_only_personalization_dictionary" />
 
-    <CheckBoxPreference
-        android:defaultValue="false"
-        android:key="boost_personalization_dictionary_for_debug"
-        android:persistent="true"
-        android:title="@string/prefs_boost_personalization_dictionary" />
-
     <PreferenceScreen
         android:key="read_external_dictionary"
         android:title="@string/prefs_read_external_dictionary" />
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 2e5e4c3..d8fb4f2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -72,13 +72,13 @@
 import com.android.inputmethod.latin.inputlogic.InputLogic;
 import com.android.inputmethod.latin.inputlogic.SpaceState;
 import com.android.inputmethod.latin.personalization.DictionaryDecayBroadcastReciever;
-import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegister;
+import com.android.inputmethod.latin.personalization.PersonalizationDictionarySessionRegistrar;
+import com.android.inputmethod.latin.personalization.PersonalizationHelper;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsActivity;
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
 import com.android.inputmethod.latin.utils.ApplicationUtils;
-import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
 import com.android.inputmethod.latin.utils.CompletionInfoUtils;
 import com.android.inputmethod.latin.utils.IntentUtils;
@@ -127,8 +127,6 @@
     private final SubtypeSwitcher mSubtypeSwitcher;
     private final SubtypeState mSubtypeState = new SubtypeState();
 
-    private UserBinaryDictionary mUserDictionary;
-
     // Object for reacting to adding/removing a dictionary pack.
     private BroadcastReceiver mDictionaryPackInstallReceiver =
             new DictionaryPackInstallBroadcastReceiver(this);
@@ -463,7 +461,6 @@
         KeyboardSwitcher.init(this);
         AudioAndHapticFeedbackManager.init(this);
         AccessibilityUtils.init(this);
-        PersonalizationDictionarySessionRegister.init(this);
 
         super.onCreate();
 
@@ -515,10 +512,18 @@
         // the layout; at this time, we need to skip resetting the contacts dictionary. It will
         // be done later inside {@see #initSuggest()} when the reopenDictionaries message is
         // processed.
+        final SettingsValues currentSettingsValues = mSettings.getCurrent();
         if (!mHandler.hasPendingReopenDictionaries() && mInputLogic.mSuggest != null) {
             // May need to reset dictionaries depending on the user settings.
+            // TODO: Quit setting dictionaries from LatinIME.
             mInputLogic.mSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
-                    mSettings.getCurrent());
+                   currentSettingsValues);
+        }
+        if (currentSettingsValues.mUsePersonalizedDicts) {
+            PersonalizationDictionarySessionRegistrar.init(this);
+        } else {
+            PersonalizationHelper.removeAllPersonalizedDictionaries(this);
+            PersonalizationDictionarySessionRegistrar.resetAll(this);
         }
     }
 
@@ -548,9 +553,9 @@
             subtypeLocale = switcherSubtypeLocale;
         }
 
-        final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
-                this /* SuggestInitializationListener */);
         final SettingsValues settingsValues = mSettings.getCurrent();
+        final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale, settingsValues,
+                this /* SuggestInitializationListener */);
         if (settingsValues.mCorrectionEnabled) {
             newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
         }
@@ -558,11 +563,8 @@
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.getInstance().initSuggest(newSuggest);
         }
-
-        mUserDictionary = new UserBinaryDictionary(this, subtypeLocale);
-        newSuggest.setUserDictionary(mUserDictionary);
-        newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */,
-                mSettings.getCurrent());
+        // TODO: Quit setting dictionaries from LatinIME.
+        newSuggest.setAdditionalDictionaries(mInputLogic.mSuggest /* oldSuggest */, settingsValues);
         final Suggest oldSuggest = mInputLogic.mSuggest;
         mInputLogic.mSuggest = newSuggest;
         if (oldSuggest != null) oldSuggest.close();
@@ -590,7 +592,7 @@
             ResearchLogger.getInstance().onDestroy();
         }
         unregisterReceiver(mDictionaryPackInstallReceiver);
-        PersonalizationDictionarySessionRegister.onDestroy(this);
+        PersonalizationDictionarySessionRegistrar.onDestroy(this);
         LatinImeLogger.commit();
         LatinImeLogger.onDestroy();
         super.onDestroy();
@@ -610,7 +612,7 @@
                 mOptionsDialog.dismiss();
             }
         }
-        PersonalizationDictionarySessionRegister.onConfigurationChanged(this, conf);
+        PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf);
         super.onConfigurationChanged(conf);
     }
 
@@ -1145,7 +1147,7 @@
 
     @Override
     public boolean onEvaluateFullscreenMode() {
-        // Reread resource value here, because this method is called by framework anytime as needed.
+        // Reread resource value here, because this method is called by the framework as needed.
         final boolean isFullscreenModeAllowed = Settings.readUseFullscreenMode(getResources());
         if (super.onEvaluateFullscreenMode() && isFullscreenModeAllowed) {
             // TODO: Remove this hack. Actually we should not really assume NO_EXTRACT_UI
@@ -1201,7 +1203,7 @@
         } else {
             wordToEdit = word;
         }
-        mUserDictionary.addWordToUserDictionary(wordToEdit);
+        mInputLogic.mSuggest.addWordToUserDictionary(wordToEdit);
     }
 
     public void displaySettingsDialog() {
@@ -1270,7 +1272,7 @@
 
     @Override
     public void onCancelBatchInput() {
-        mInputLogic.onCancelBatchInput(mInputUpdater);
+        mInputLogic.onCancelBatchInput(mHandler, mInputUpdater);
     }
 
     // TODO[IL]: Make this a package-private standalone class in inputlogic/ and remove all
@@ -1308,17 +1310,15 @@
             return true;
         }
 
-        // Run in the UI thread.
+        // Run on the UI thread.
         public void onStartBatchInput() {
             synchronized (mLock) {
                 mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
                 mInBatchInput = true;
-                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                        SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
             }
         }
 
-        // Run in the Handler thread.
+        // Run on the Handler thread.
         private void updateBatchInput(final InputPointers batchPointers, final int sequenceNumber) {
             synchronized (mLock) {
                 if (!mInBatchInput) {
@@ -1337,7 +1337,7 @@
             }
         }
 
-        // Run in the UI thread.
+        // Run on the UI thread.
         public void onUpdateBatchInput(final InputPointers batchPointers,
                 final int sequenceNumber) {
             if (mHandler.hasMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP)) {
@@ -1350,12 +1350,10 @@
         public void onCancelBatchInput() {
             synchronized (mLock) {
                 mInBatchInput = false;
-                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                        SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
             }
         }
 
-        // Run in the UI thread.
+        // Run on the UI thread.
         public void onEndBatchInput(final InputPointers batchPointers) {
             synchronized(mLock) {
                 getSuggestedWordsGestureLocked(batchPointers, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
@@ -1405,7 +1403,7 @@
         }
     }
 
-    // This method must run in UI Thread.
+    // This method must run on the UI Thread.
     private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords,
             final boolean dismissGestureFloatingPreviewText) {
         showSuggestionStrip(suggestedWords);
@@ -1416,7 +1414,7 @@
         }
     }
 
-    // This method must run in UI Thread.
+    // This method must run on the UI Thread.
     public void onEndBatchInputAsyncInternal(final SuggestedWords suggestedWords) {
         final String batchInputText = suggestedWords.isEmpty() ? null : suggestedWords.getWord(0);
         if (TextUtils.isEmpty(batchInputText)) {
@@ -1613,19 +1611,6 @@
                 false /* isPrediction */);
     }
 
-    private void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) {
-        if (suggestedWords.isEmpty()) return;
-        final String autoCorrection;
-        if (suggestedWords.mWillAutoCorrect) {
-            autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
-        } else {
-            // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
-            // because it may differ from mWordComposer.mTypedWord.
-            autoCorrection = typedWord;
-        }
-        mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
-    }
-
     private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords,
             final String typedWord) {
       if (suggestedWords.isEmpty()) {
@@ -1634,10 +1619,17 @@
           clearSuggestionStrip();
           return;
       }
-      setAutoCorrection(suggestedWords, typedWord);
-      final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
-      setSuggestedWords(suggestedWords, isAutoCorrection);
-      setAutoCorrectionIndicator(isAutoCorrection);
+      final String autoCorrection;
+      if (suggestedWords.mWillAutoCorrect) {
+          autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+      } else {
+          // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
+          // because it may differ from mWordComposer.mTypedWord.
+          autoCorrection = typedWord;
+      }
+      mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
+      setSuggestedWords(suggestedWords, suggestedWords.mWillAutoCorrect);
+      setAutoCorrectionIndicator(suggestedWords.mWillAutoCorrect);
       setSuggestionStripShown(isSuggestionsStripVisible());
       // An auto-correction is available, cache it in accessibility code so
       // we can be speak it if the user touches a key that will insert it.
@@ -1732,13 +1724,13 @@
                         || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
                         && suggest != null
                         // If the suggestion is not in the dictionary, the hint should be shown.
-                        && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true);
+                        && !suggest.isValidWord(suggestion, true);
 
         if (currentSettings.mIsInternal) {
             LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
-        if (showingAddToDictionaryHint && mUserDictionary.mEnabled) {
+        if (showingAddToDictionaryHint && suggest.isUserDictionaryEnabled()) {
             mSuggestionStripView.showAddToDictionaryHint(
                     suggestion, currentSettings.mHintToSaveText);
         } else {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 32ab1f3..1684122 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
-import android.preference.PreferenceManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -27,7 +26,6 @@
 import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
 import com.android.inputmethod.latin.personalization.PersonalizationHelper;
 import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
-import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.BoundedTreeSet;
@@ -39,11 +37,13 @@
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This class loads a dictionary and provides a list of suggestions for a given sequence of
  * characters. This includes corrections and completions.
  */
+// TODO: Separate dictionary operations from suggestions handling logic.
 public final class Suggest {
     public static final String TAG = Suggest.class.getSimpleName();
 
@@ -74,6 +74,7 @@
     private HashSet<String> mOnlyDictionarySetForDebug = null;
     private Dictionary mMainDictionary;
     private ContactsBinaryDictionary mContactsDictionary;
+    private UserBinaryDictionary mUserDictionary;
     private UserHistoryDictionary mUserHistoryDictionary;
     private PersonalizationDictionary mPersonalizationDictionary;
     @UsedForTesting
@@ -86,18 +87,17 @@
 
     private final Context mContext;
 
-    public Suggest(final Context context, final Locale locale,
+    public Suggest(final Context context, final Locale locale, final SettingsValues settingsValues,
             final SuggestInitializationListener listener) {
         initAsynchronously(context, locale, listener);
         mLocale = locale;
         mContext = context;
-        // TODO: Use SettingsValues instead of Settings.
         // initialize a debug flag for the personalization
-        if (Settings.readUseOnlyPersonalizationDictionaryForDebug(
-                PreferenceManager.getDefaultSharedPreferences(context))) {
+        if (settingsValues.mUseOnlyPersonalizationDictionaryForDebug) {
             mOnlyDictionarySetForDebug = new HashSet<String>();
             mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
         }
+        setUserDictionary(new UserBinaryDictionary(context, locale));
     }
 
     @UsedForTesting
@@ -171,27 +171,13 @@
         return mMainDictionary;
     }
 
-    public ContactsBinaryDictionary getContactsDictionary() {
-        return mContactsDictionary;
-    }
-
-    public UserHistoryDictionary getUserHistoryDictionary() {
-        return mUserHistoryDictionary;
-    }
-
-    public PersonalizationDictionary getPersonalizationDictionary() {
-        return mPersonalizationDictionary;
-    }
-
-    public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
-        return mDictionaries;
-    }
-
     /**
      * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted
      * before the main dictionary, if set. This refers to the system-managed user dictionary.
      */
+    @UsedForTesting
     public void setUserDictionary(final UserBinaryDictionary userDictionary) {
+        mUserDictionary = userDictionary;
         addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary);
     }
 
@@ -200,17 +186,18 @@
      * the contacts dictionary by passing null to this method. In this case no contacts dictionary
      * won't be used.
      */
+    @UsedForTesting
     public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
         mContactsDictionary = contactsDictionary;
         addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
     }
 
-    public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
+    private void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
         mUserHistoryDictionary = userHistoryDictionary;
         addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
     }
 
-    public void setPersonalizationDictionary(
+    private void setPersonalizationDictionary(
             final PersonalizationDictionary personalizationDictionary) {
         mPersonalizationDictionary = personalizationDictionary;
         addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION, personalizationDictionary);
@@ -225,7 +212,7 @@
     public void setAdditionalDictionaries(final Suggest oldSuggest,
             final SettingsValues settingsValues) {
         // Contacts dictionary
-        resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null,
+        resetContactsDictionary(null != oldSuggest ? oldSuggest.mContactsDictionary : null,
                 settingsValues);
         // User history dictionary & Personalization dictionary
         resetPersonalizedDictionaries(oldSuggest, settingsValues);
@@ -245,9 +232,9 @@
         final boolean shouldSetDictionaries = settingsValues.mUsePersonalizedDicts;
 
         final UserHistoryDictionary oldUserHistoryDictionary = (null == oldSuggest) ? null :
-                oldSuggest.getUserHistoryDictionary();
+                oldSuggest.mUserHistoryDictionary;
         final PersonalizationDictionary oldPersonalizationDictionary = (null == oldSuggest) ? null :
-                oldSuggest.getPersonalizationDictionary();
+                oldSuggest.mPersonalizationDictionary;
         final UserHistoryDictionary userHistoryDictionaryToUse;
         final PersonalizationDictionary personalizationDictionaryToUse;
         if (!shouldSetDictionaries) {
@@ -311,6 +298,43 @@
         setContactsDictionary(dictionaryToUse);
     }
 
+    public boolean isUserDictionaryEnabled() {
+        if (mUserDictionary == null) {
+            return false;
+        }
+        return mUserDictionary.mEnabled;
+    }
+
+    public void addWordToUserDictionary(String word) {
+        if (mUserDictionary == null) {
+            return;
+        }
+        mUserDictionary.addWordToUserDictionary(word);
+    }
+
+    public String addToUserHistory(final WordComposer wordComposer, final String previousWord,
+            final String suggestion) {
+        if (mUserHistoryDictionary == null) {
+            return null;
+        }
+        final String secondWord;
+        if (wordComposer.wasAutoCapitalized() && !wordComposer.isMostlyCaps()) {
+            secondWord = suggestion.toLowerCase(mLocale);
+        } else {
+            secondWord = suggestion;
+        }
+        // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
+        // We don't add words with 0-frequency (assuming they would be profanity etc.).
+        final int maxFreq = getMaxFrequency(suggestion);
+        if (maxFreq == 0) {
+            return null;
+        }
+        final boolean isValid = maxFreq > 0;
+        final int timeStamp = (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis()));
+        mUserHistoryDictionary.addToDictionary(previousWord, secondWord, isValid, timeStamp);
+        return previousWord;
+    }
+
     public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
         if (mUserHistoryDictionary != null) {
             mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
@@ -389,8 +413,8 @@
         // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
         final boolean allowsToBeAutoCorrected = (null != whitelistedWord
                 && !whitelistedWord.equals(consideredWord))
-                || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord(this,
-                        consideredWord, wordComposer.isFirstCharCapitalized()));
+                || (consideredWord.length() > 1
+                        && !isValidWord(consideredWord, wordComposer.isFirstCharCapitalized()));
 
         final boolean hasAutoCorrection;
         // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
@@ -594,6 +618,45 @@
                 wordInfo.mAutoCommitFirstWordConfidence);
     }
 
+    public boolean isValidWord(final String word, final boolean ignoreCase) {
+        if (TextUtils.isEmpty(word)) {
+            return false;
+        }
+        final String lowerCasedWord = word.toLowerCase(mLocale);
+        for (final String key : mDictionaries.keySet()) {
+            final Dictionary dictionary = mDictionaries.get(key);
+            // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
+            // managing to get null in here. Presumably the language is changing to a language with
+            // no main dictionary and the monkey manages to type a whole word before the thread
+            // that reads the dictionary is started or something?
+            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+            // would be immutable once it's finished initializing, but concretely a null test is
+            // probably good enough for the time being.
+            if (null == dictionary) continue;
+            if (dictionary.isValidWord(word)
+                    || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int getMaxFrequency(final String word) {
+        if (TextUtils.isEmpty(word)) {
+            return Dictionary.NOT_A_PROBABILITY;
+        }
+        int maxFreq = -1;
+        for (final String key : mDictionaries.keySet()) {
+            final Dictionary dictionary = mDictionaries.get(key);
+            if (null == dictionary) continue;
+            final int tempFreq = dictionary.getFrequency(word);
+            if (tempFreq >= maxFreq) {
+                maxFreq = tempFreq;
+            }
+        }
+        return maxFreq;
+    }
+
     public void close() {
         final HashSet<Dictionary> dictionaries = CollectionUtils.newHashSet();
         dictionaries.addAll(mDictionaries.values());
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 97c89dd4..f9de89c 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -104,10 +104,6 @@
         return debugString;
     }
 
-    public boolean willAutoCorrect() {
-        return mWillAutoCorrect;
-    }
-
     @Override
     public String toString() {
         // Pretty-print method to help debug
@@ -150,7 +146,7 @@
         for (int index = 1; index < previousSize; index++) {
             final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index);
             final String prevWord = prevWordInfo.mWord;
-            // Filter out duplicate suggestion.
+            // Filter out duplicate suggestions.
             if (!alreadySeen.contains(prevWord)) {
                 suggestionsList.add(prevWordInfo);
                 alreadySeen.add(prevWord);
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index df34687..4a4abd7 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -44,12 +44,10 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
 import com.android.inputmethod.latin.utils.AsyncResultHolder;
-import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
 import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
@@ -60,7 +58,6 @@
 
 import java.util.ArrayList;
 import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
 
 /**
  * This class manages the input logic.
@@ -71,6 +68,8 @@
     // TODO : Remove this member when we can.
     private final LatinIME mLatinIME;
 
+    private InputLogicHandler mInputLogicHandler;
+
     // TODO : make all these fields private as soon as possible.
     // Current space state of the input method. This can be any of the above constants.
     public int mSpaceState;
@@ -105,6 +104,7 @@
         mWordComposer = new WordComposer();
         mEventInterpreter = new EventInterpreter(latinIME);
         mConnection = new RichInputConnection(latinIME);
+        mInputLogicHandler = null;
     }
 
     /**
@@ -119,12 +119,15 @@
      * @param restarting whether input is starting in the same field as before.
      */
     public void startInput(final boolean restarting) {
+        mInputLogicHandler = new InputLogicHandler();
     }
 
     /**
      * Clean up the input logic after input is finished.
      */
     public void finishInput() {
+        mInputLogicHandler.destroy();
+        mInputLogicHandler = null;
     }
 
     /**
@@ -300,6 +303,8 @@
             final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler,
             final LatinIME.InputUpdater inputUpdater) {
         inputUpdater.onStartBatchInput();
+        handler.showGesturePreviewAndSuggestionStrip(
+                SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
         handler.cancelUpdateSuggestionStrip();
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
@@ -400,9 +405,12 @@
         inputUpdater.onEndBatchInput(batchPointers);
     }
 
-    // TODO: remove this argument
-    public void onCancelBatchInput(final LatinIME.InputUpdater inputUpdater) {
+    // TODO: remove these arguments
+    public void onCancelBatchInput(final LatinIME.UIHandler handler,
+            final LatinIME.InputUpdater inputUpdater) {
         inputUpdater.onCancelBatchInput();
+        handler.showGesturePreviewAndSuggestionStrip(
+                SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
     }
 
     /**
@@ -977,24 +985,8 @@
         final Suggest suggest = mSuggest;
         if (suggest == null) return null;
 
-        final UserHistoryDictionary userHistoryDictionary = suggest.getUserHistoryDictionary();
-        if (userHistoryDictionary == null) return null;
-
         final String prevWord = mConnection.getNthPreviousWord(settingsValues, 2);
-        final String secondWord;
-        if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
-            secondWord = suggestion.toLowerCase(settingsValues.mLocale);
-        } else {
-            secondWord = suggestion;
-        }
-        // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
-        // We don't add words with 0-frequency (assuming they would be profanity etc.).
-        final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
-                suggest.getUnigramDictionaries(), suggestion);
-        if (maxFreq == 0) return null;
-        userHistoryDictionary.addToDictionary(prevWord, secondWord, maxFreq > 0,
-                (int)TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis())));
-        return prevWord;
+        return suggest.addToUserHistory(mWordComposer, prevWord, suggestion);
     }
 
     public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
new file mode 100644
index 0000000..d611e4b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+
+/**
+ * A helper to manage deferred tasks for the input logic.
+ */
+// TODO: Make this package private
+public class InputLogicHandler implements Handler.Callback {
+    final Handler mNonUIThreadHandler;
+
+    public InputLogicHandler() {
+        final HandlerThread handlerThread = new HandlerThread(
+                InputLogicHandler.class.getSimpleName());
+        handlerThread.start();
+        mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this);
+    }
+
+    public void destroy() {
+        mNonUIThreadHandler.getLooper().quit();
+    }
+
+    /**
+     * Handle a message.
+     * @see android.os.Handler.Callback#handleMessage(android.os.Message)
+     */
+    @Override
+    public boolean handleMessage(final Message msg) {
+        return true;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index 596562f..9b2b981 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -27,7 +27,7 @@
 import android.content.Context;
 
 public class PersonalizationDictionary extends DecayingExpandableBinaryDictionaryBase {
-    private static final String NAME = PersonalizationDictionary.class.getSimpleName();
+    /* package */ static final String NAME = PersonalizationDictionary.class.getSimpleName();
 
     private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
             CollectionUtils.newArrayList();
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
similarity index 90%
rename from java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
rename to java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
index 542bda6..7696511 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegister.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 
-public class PersonalizationDictionarySessionRegister {
+public class PersonalizationDictionarySessionRegistrar {
     public static void init(final Context context) {
     }
 
@@ -32,6 +32,9 @@
     public static void onRemoveData(final Context context, final String type) {
     }
 
+    public static void resetAll(final Context context) {
+    }
+
     public static void onDestroy(final Context context) {
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index d55cae1..38b22e5 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -17,10 +17,13 @@
 package com.android.inputmethod.latin.personalization;
 
 import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.FileUtils;
 
 import android.content.Context;
 import android.util.Log;
 
+import java.io.File;
+import java.io.FilenameFilter;
 import java.lang.ref.SoftReference;
 import java.util.Locale;
 import java.util.concurrent.ConcurrentHashMap;
@@ -96,4 +99,47 @@
             return dict;
         }
     }
+
+    public static void removeAllPersonalizedDictionaries(final Context context) {
+        removeAllDictionaries(context, sLangUserHistoryDictCache,
+                UserHistoryDictionary.NAME);
+        removeAllDictionaries(context, sLangPersonalizationDictCache,
+                PersonalizationDictionary.NAME);
+    }
+
+    private static <T extends DecayingExpandableBinaryDictionaryBase> void removeAllDictionaries(
+            final Context context, final ConcurrentHashMap<String, SoftReference<T>> dictionaryMap,
+            final String dictNamePrefix) {
+        synchronized (dictionaryMap) {
+            for (final ConcurrentHashMap.Entry<String, SoftReference<T>> entry
+                    : dictionaryMap.entrySet()) {
+                if (entry.getValue() != null) {
+                    final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get();
+                    if (dict != null) {
+                        dict.clearAndFlushDictionary();
+                    }
+                }
+            }
+            dictionaryMap.clear();
+            if (!FileUtils.deleteFilteredFiles(
+                    context.getFilesDir(), new DictFilter(dictNamePrefix))) {
+                Log.e(TAG, "Cannot remove all existing dictionary files. filesDir: "
+                        + context.getFilesDir().getAbsolutePath() + ", dictNamePrefix: "
+                        + dictNamePrefix);
+            }
+        }
+    }
+
+    private static class DictFilter implements FilenameFilter {
+        private final String mName;
+
+        DictFilter(final String name) {
+            mName = name;
+        }
+
+        @Override
+        public boolean accept(final File dir, final String name) {
+            return name.startsWith(mName);
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 868f21c..c23bc9b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -29,8 +29,7 @@
  * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
  */
 public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
-    /* package for tests */ static final String NAME =
-            UserHistoryDictionary.class.getSimpleName();
+    /* package */ static final String NAME = UserHistoryDictionary.class.getSimpleName();
     /* package */ UserHistoryDictionary(final Context context, final Locale locale) {
         super(context, locale, Dictionary.TYPE_USER_HISTORY, getDictNameWithLocale(NAME, locale));
     }
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index d060485..29bbed8 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -39,8 +39,6 @@
     public static final String PREF_STATISTICS_LOGGING = "enable_logging";
     public static final String PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
             "use_only_personalization_dictionary_for_debug";
-    public static final String PREF_BOOST_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
-            "boost_personalization_dictionary_for_debug";
     private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
     private static final boolean SHOW_STATISTICS_LOGGING = false;
 
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 9bd2b93..75c7258 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -358,21 +358,13 @@
         return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false);
     }
 
-    public static boolean readUseOnlyPersonalizationDictionaryForDebug(
-            final SharedPreferences prefs) {
-        return prefs.getBoolean(
-                DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
-    }
-
-    public static boolean readBoostPersonalizationDictionaryForDebug(
-            final SharedPreferences prefs) {
-        return prefs.getBoolean(
-                DebugSettings.PREF_BOOST_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
-    }
-
     public void writeLastUsedPersonalizationToken(byte[] token) {
-        final String tokenStr = StringUtils.byteArrayToHexString(token);
-        mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply();
+        if (token == null) {
+            mPrefs.edit().remove(PREF_LAST_USED_PERSONALIZATION_TOKEN).apply();
+        } else {
+            final String tokenStr = StringUtils.byteArrayToHexString(token);
+            mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply();
+        }
     }
 
     public byte[] readLastUsedPersonalizationToken() {
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 3307de8..a07a0ce 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -99,7 +99,6 @@
     public final float mAutoCorrectionThreshold;
     public final boolean mCorrectionEnabled;
     public final int mSuggestionVisibility;
-    public final boolean mBoostPersonalizationDictionaryForDebug;
     public final boolean mUseOnlyPersonalizationDictionaryForDebug;
     public final int mDisplayOrientation;
     private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
@@ -181,10 +180,8 @@
         AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
                 prefs, mAdditionalFeaturesSettingValues);
         mIsInternal = Settings.isInternal(prefs);
-        mBoostPersonalizationDictionaryForDebug =
-                Settings.readBoostPersonalizationDictionaryForDebug(prefs);
-        mUseOnlyPersonalizationDictionaryForDebug =
-                Settings.readUseOnlyPersonalizationDictionaryForDebug(prefs);
+        mUseOnlyPersonalizationDictionaryForDebug = prefs.getBoolean(
+                DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
         mDisplayOrientation = res.getConfiguration().orientation;
         mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>();
         final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(
@@ -241,7 +238,6 @@
         mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
         mSuggestionVisibility = 0;
         mIsInternal = false;
-        mBoostPersonalizationDictionaryForDebug = false;
         mUseOnlyPersonalizationDictionaryForDebug = false;
         mDisplayOrientation = Configuration.ORIENTATION_PORTRAIT;
         mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>();
@@ -321,7 +317,7 @@
     public boolean isBrokenByRecorrection() {
         final AppWorkaroundsUtils appWorkaroundUtils
                 = mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE);
-        return null == appWorkaroundUtils ? null : appWorkaroundUtils.isBrokenByRecorrection();
+        return null == appWorkaroundUtils ? false : appWorkaroundUtils.isBrokenByRecorrection();
     }
 
     // Helper functions to create member values.
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 72281e6..f836e61 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -208,7 +208,7 @@
         }
         final String word = suggestedWords.getWord(indexInSuggestedWords);
         final boolean isAutoCorrect = indexInSuggestedWords == 1
-                && suggestedWords.willAutoCorrect();
+                && suggestedWords.mWillAutoCorrect;
         final boolean isTypedWordValid = indexInSuggestedWords == 0
                 && suggestedWords.mTypedWordValid;
         if (!isAutoCorrect && !isTypedWordValid) {
@@ -232,7 +232,7 @@
             final SuggestedWords suggestedWords) {
         final int indexToDisplayMostImportantSuggestion;
         final int indexToDisplaySecondMostImportantSuggestion;
-        if (suggestedWords.willAutoCorrect()) {
+        if (suggestedWords.mWillAutoCorrect) {
             indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
             indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
         } else {
@@ -257,7 +257,7 @@
         final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD);
 
         final int color;
-        if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) {
+        if (positionInStrip == mCenterPositionInStrip && suggestedWords.mWillAutoCorrect) {
             color = mColorAutoCorrect;
         } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) {
             color = mColorValidTypedWord;
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 066c5fd..37c173f 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -17,16 +17,11 @@
 package com.android.inputmethod.latin.utils;
 
 import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
-import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.concurrent.ConcurrentHashMap;
-
 public final class AutoCorrectionUtils {
     private static final boolean DBG = LatinImeLogger.sDBG;
     private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
@@ -36,48 +31,6 @@
         // Purely static class: can't instantiate.
     }
 
-    public static boolean isValidWord(final Suggest suggest, final String word,
-            final boolean ignoreCase) {
-        if (TextUtils.isEmpty(word)) {
-            return false;
-        }
-        final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries();
-        final String lowerCasedWord = word.toLowerCase(suggest.mLocale);
-        for (final String key : dictionaries.keySet()) {
-            final Dictionary dictionary = dictionaries.get(key);
-            // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
-            // managing to get null in here. Presumably the language is changing to a language with
-            // no main dictionary and the monkey manages to type a whole word before the thread
-            // that reads the dictionary is started or something?
-            // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
-            // would be immutable once it's finished initializing, but concretely a null test is
-            // probably good enough for the time being.
-            if (null == dictionary) continue;
-            if (dictionary.isValidWord(word)
-                    || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries,
-            final String word) {
-        if (TextUtils.isEmpty(word)) {
-            return Dictionary.NOT_A_PROBABILITY;
-        }
-        int maxFreq = -1;
-        for (final String key : dictionaries.keySet()) {
-            final Dictionary dictionary = dictionaries.get(key);
-            if (null == dictionary) continue;
-            final int tempFreq = dictionary.getFrequency(word);
-            if (tempFreq >= maxFreq) {
-                maxFreq = tempFreq;
-            }
-        }
-        return maxFreq;
-    }
-
     public static boolean suggestionExceedsAutoCorrectionThreshold(
             final SuggestedWordInfo suggestion, final String consideredWord,
             final float autoCorrectionThreshold) {
diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
index 83c1e7c..22b0fbb 100644
--- a/java/src/com/android/inputmethod/latin/utils/FileUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin.utils;
 
 import java.io.File;
+import java.io.FilenameFilter;
 
 /**
  * A simple class to help with removing directories recursively.
@@ -30,4 +31,18 @@
         }
         return path.delete();
     }
+
+    public static boolean deleteFilteredFiles(final File dir, final FilenameFilter fileNameFilter) {
+        if (!dir.isDirectory()) {
+            return false;
+        }
+        final File[] files = dir.listFiles(fileNameFilter);
+        boolean hasDeletedAllFiles = true;
+        for (final File file : files) {
+            if (!deleteRecursively(file)) {
+                hasDeletedAllFiles = false;
+            }
+        }
+        return hasDeletedAllFiles;
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java b/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java
index 5e98cdf..db14b83 100644
--- a/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java
+++ b/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java
@@ -41,7 +41,7 @@
             }
         }
     }
-    public void testSwitchLanguagesAndInputRandamCodePoints() {
+    public void testSwitchLanguagesAndInputRandomCodePoints() {
         final String[] locales = {"en_US", "de", "el", "es", "fi", "it", "nl", "pt", "ru"};
         final int switchCount = 50;
         final int maxWordCountToTypeInEachIteration = 20;