Merge "Show more messages when reading a compressed combined format file."
diff --git a/dictionaries/en_GB_wordlist.combined.gz b/dictionaries/en_GB_wordlist.combined.gz
index 839f3ef..4f008ed 100644
--- a/dictionaries/en_GB_wordlist.combined.gz
+++ b/dictionaries/en_GB_wordlist.combined.gz
Binary files differ
diff --git a/java/res/layout/emoji_palettes_view.xml b/java/res/layout/emoji_palettes_view.xml
index 6133326..1c6da90 100644
--- a/java/res/layout/emoji_palettes_view.xml
+++ b/java/res/layout/emoji_palettes_view.xml
@@ -101,10 +101,10 @@
             android:layout_weight="0.70"
             android:layout_height="match_parent" />
         <ImageButton
-            android:id="@+id/emoji_keyboard_send"
+            android:id="@+id/emoji_keyboard_alphabet2"
             android:layout_width="0dip"
             android:layout_weight="0.15"
             android:layout_height="match_parent"
-            android:src="@drawable/sym_keyboard_return_holo_dark" />
+            android:src="@drawable/ic_ime_switcher_dark" />
     </LinearLayout>
 </com.android.inputmethod.keyboard.EmojiPalettesView>
diff --git a/java/res/values-hy/donottranslate.xml b/java/res/values-hy/donottranslate.xml
index 4a6d188..7b0c566 100644
--- a/java/res/values-hy/donottranslate.xml
+++ b/java/res/values-hy/donottranslate.xml
@@ -26,4 +26,7 @@
     <!-- Symbols that separate words. Adding armenian period and comma. -->
     <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
     <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"&#x0589;&#x055D;</string>
+    <!-- The sentence separator code point, for capitalization -->
+    <!-- U+0589: "։" ARMENIAN FULL STOP   ; 589h = 1417d -->
+    <integer name="sentence_separator">1417</integer>
 </resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index e5fe564..31945d0 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -413,7 +413,6 @@
         </attr>
         <attr name="navigateNext" format="boolean" />
         <attr name="navigatePrevious" format="boolean" />
-        <attr name="navigateEmoji" format="boolean" />
         <attr name="passwordInput" format="boolean" />
         <attr name="clobberSettingsKey" format="boolean" />
         <attr name="shortcutKeyEnabled" format="boolean" />
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 42e692d..4733aa2 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -31,6 +31,9 @@
     <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
     <!-- Word connectors -->
     <string name="symbols_word_connectors">\'-</string>
+    <!-- The sentence separator code point, for capitalization -->
+    <!-- U+002E: "." FULL STOP   ; 2Eh = 46d -->
+    <integer name="sentence_separator">46</integer>
     <!-- Whether this language uses spaces between words -->
     <bool name="current_language_has_spaces">true</bool>
 
diff --git a/java/res/xml/key_styles_enter.xml b/java/res/xml/key_styles_enter.xml
index 68c0a23..568c602 100644
--- a/java/res/xml/key_styles_enter.xml
+++ b/java/res/xml/key_styles_enter.xml
@@ -21,13 +21,14 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <!-- TODO: Stop using many conditional cases for emoji_key_as_more_key. There are way too many to maintain. -->
     <!-- Navigate more keys style -->
     <switch>
-        <!-- navigateEmoji="false" -->
+        <!-- latin:passwordInput="true" -->
         <case
             latin:imeAction="actionNext"
             latin:navigatePrevious="true"
-            latin:navigateEmoji="false"
+            latin:passwordInput="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -37,7 +38,7 @@
         <case
             latin:imeAction="actionNext"
             latin:navigatePrevious="false"
-            latin:navigateEmoji="false"
+            latin:passwordInput="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle" />
@@ -45,7 +46,7 @@
         <case
             latin:imeAction="actionPrevious"
             latin:navigateNext="true"
-            latin:navigateEmoji="false"
+            latin:passwordInput="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -55,16 +56,15 @@
         <case
             latin:imeAction="actionPrevious"
             latin:navigateNext="false"
-            latin:navigateEmoji="false"
+            latin:passwordInput="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle" />
         </case>
-        <!-- imeAction!="actionNext" and imeAction!="actionPrevious" -->
         <case
             latin:navigateNext="true"
             latin:navigatePrevious="true"
-            latin:navigateEmoji="false"
+            latin:passwordInput="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -74,7 +74,7 @@
         <case
             latin:navigateNext="true"
             latin:navigatePrevious="false"
-            latin:navigateEmoji="false"
+            latin:passwordInput="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -84,7 +84,7 @@
         <case
             latin:navigateNext="false"
             latin:navigatePrevious="true"
-            latin:navigateEmoji="false"
+            latin:passwordInput="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -94,16 +94,90 @@
         <case
             latin:navigateNext="false"
             latin:navigatePrevious="false"
-            latin:navigateEmoji="false"
+            latin:passwordInput="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle" />
         </case>
-        <!-- navigateEmoji="true" -->
+        <!-- latin:mode="email|url|phone|number|date|time|datetime" -->
         <case
             latin:imeAction="actionNext"
             latin:navigatePrevious="true"
-            latin:navigateEmoji="true"
+            latin:mode="email|url|phone|number|date|time|datetime"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="!text/action_previous_as_more_key" />
+        </case>
+        <case
+            latin:imeAction="actionNext"
+            latin:navigatePrevious="false"
+            latin:mode="email|url|phone|number|date|time|datetime"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle" />
+        </case>
+        <case
+            latin:imeAction="actionPrevious"
+            latin:navigateNext="true"
+            latin:mode="email|url|phone|number|date|time|datetime"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="!text/action_next_as_more_key" />
+        </case>
+        <case
+            latin:imeAction="actionPrevious"
+            latin:navigateNext="false"
+            latin:mode="email|url|phone|number|date|time|datetime"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle" />
+        </case>
+        <case
+            latin:navigateNext="true"
+            latin:navigatePrevious="true"
+            latin:mode="email|url|phone|number|date|time|datetime"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="!fixedColumnOrder!2,!needsDividers!,!text/action_previous_as_more_key,!text/action_next_as_more_key" />
+        </case>
+        <case
+            latin:navigateNext="true"
+            latin:navigatePrevious="false"
+            latin:mode="email|url|phone|number|date|time|datetime"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="!text/action_next_as_more_key" />
+        </case>
+        <case
+            latin:navigateNext="false"
+            latin:navigatePrevious="true"
+            latin:mode="email|url|phone|number|date|time|datetime"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle"
+                latin:keyLabelFlags="hasPopupHint|preserveCase"
+                latin:moreKeys="!text/action_previous_as_more_key" />
+        </case>
+        <case
+            latin:navigateNext="false"
+            latin:navigatePrevious="false"
+            latin:mode="email|url|phone|number|date|time|datetime"
+        >
+            <key-style
+                latin:styleName="navigateMoreKeysStyle" />
+        </case>
+        <!-- default -->
+        <case
+            latin:imeAction="actionNext"
+            latin:navigatePrevious="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -113,7 +187,6 @@
         <case
             latin:imeAction="actionNext"
             latin:navigatePrevious="false"
-            latin:navigateEmoji="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -122,7 +195,6 @@
         <case
             latin:imeAction="actionPrevious"
             latin:navigateNext="true"
-            latin:navigateEmoji="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -132,17 +204,14 @@
         <case
             latin:imeAction="actionPrevious"
             latin:navigateNext="false"
-            latin:navigateEmoji="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
                 latin:moreKeys="!text/emoji_key_as_more_key" />
         </case>
-        <!-- imeAction!="actionNext" and imeAction!="actionPrevious" -->
         <case
             latin:navigateNext="true"
             latin:navigatePrevious="true"
-            latin:navigateEmoji="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -152,7 +221,6 @@
         <case
             latin:navigateNext="true"
             latin:navigatePrevious="false"
-            latin:navigateEmoji="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -162,7 +230,6 @@
         <case
             latin:navigateNext="false"
             latin:navigatePrevious="true"
-            latin:navigateEmoji="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
@@ -172,7 +239,6 @@
         <case
             latin:navigateNext="false"
             latin:navigatePrevious="false"
-            latin:navigateEmoji="true"
         >
             <key-style
                 latin:styleName="navigateMoreKeysStyle"
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
index 46e72bf..85ae500 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
@@ -484,10 +484,10 @@
         spaceKey.setTag(Constants.CODE_SPACE);
         spaceKey.setOnClickListener(this);
         emojiLp.setKeyProperties(spaceKey);
-        final ImageView sendKey = (ImageView)findViewById(R.id.emoji_keyboard_send);
-        sendKey.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
-        sendKey.setTag(Constants.CODE_ENTER);
-        sendKey.setOnClickListener(this);
+        final ImageView alphabetKey2 = (ImageView)findViewById(R.id.emoji_keyboard_alphabet2);
+        alphabetKey2.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
+        alphabetKey2.setTag(Constants.CODE_SWITCH_ALPHA_SYMBOL);
+        alphabetKey2.setOnClickListener(this);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index 08302a7..09766ac 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -42,7 +42,6 @@
     private final Object mLock = new Object();
 
     private final SharedPreferences mPrefs;
-    private final int mLeftPadding;
     private final int mHorizontalStep;
     private final int mVerticalStep;
     private final int mColumnsNum;
@@ -58,7 +57,6 @@
         super(templateKeyboard);
         final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
         final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
-        mLeftPadding = key0.getX();
         mHorizontalStep = Math.abs(key1.getX() - key0.getX());
         mVerticalStep = key0.getHeight() + mVerticalGap;
         mColumnsNum = mBaseWidth / mHorizontalStep;
@@ -122,9 +120,11 @@
             }
             int index = 0;
             for (final GridKey gridKey : mGridKeys) {
-                final int keyX = getKeyX(index);
-                final int keyY = getKeyY(index);
-                gridKey.updateCorrdinates(keyX, keyY);
+                final int keyX0 = getKeyX0(index);
+                final int keyY0 = getKeyY0(index);
+                final int keyX1 = getKeyX1(index);
+                final int keyY1 = getKeyY1(index);
+                gridKey.updateCorrdinates(keyX0, keyY0, keyX1, keyY1);
                 index++;
             }
         }
@@ -172,14 +172,24 @@
         }
     }
 
-    private int getKeyX(final int index) {
+    private int getKeyX0(final int index) {
         final int column = index % mColumnsNum;
-        return column * mHorizontalStep + mLeftPadding;
+        return column * mHorizontalStep;
     }
 
-    private int getKeyY(final int index) {
+    private int getKeyX1(final int index) {
+        final int column = index % mColumnsNum + 1;
+        return column * mHorizontalStep;
+    }
+
+    private int getKeyY0(final int index) {
         final int row = index / mColumnsNum;
-        return row * mVerticalStep + mTopPadding;
+        return row * mVerticalStep;
+    }
+
+    private int getKeyY1(final int index) {
+        final int row = index / mColumnsNum + 1;
+        return row * mVerticalStep;
     }
 
     @Override
@@ -207,10 +217,10 @@
             super(originalKey);
         }
 
-        public void updateCorrdinates(final int x, final int y) {
-            mCurrentX = x;
-            mCurrentY = y;
-            getHitBox().set(x, y, x + getWidth(), y + getHeight());
+        public void updateCorrdinates(final int x0, final int y0, final int x1, final int y1) {
+            mCurrentX = x0;
+            mCurrentY = y0;
+            getHitBox().set(x0, y0, x1, y1);
         }
 
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 506dfa7..9f9fdaa 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -178,8 +178,6 @@
             if (!state.mIsAlphabetShiftLocked) {
                 setShifted(state.mShiftMode);
             }
-            // TODO: is this the right place to do this? Should we do this in setShift* instead?
-            mSwitchActions.requestUpdatingShiftState();
         } else {
             mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
         }
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
index 4a0ce37..463d093 100644
--- a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -41,8 +41,17 @@
 
     abstract public void clear();
 
+    /**
+     * Add a unigram with an optional shortcut to the dictionary.
+     * @param word The word to add.
+     * @param shortcutTarget A shortcut target for this word, or null if none.
+     * @param frequency The frequency for this unigram.
+     * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
+     *   if shortcutTarget is null.
+     * @param isNotAWord true if this is not a word, i.e. shortcut only.
+     */
     abstract public void addUnigramWord(final String word, final String shortcutTarget,
-            final int frequency, final boolean isNotAWord);
+            final int frequency, final int shortcutFreq, final boolean isNotAWord);
 
     // TODO: Remove lastModifiedTime after making binary dictionary support forgetting curve.
     abstract public void addBigramWords(final String word0, final String word1,
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 541e697..fd29698 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -52,6 +52,10 @@
     public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
     @UsedForTesting
     public static final String BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
+    @UsedForTesting
+    public static final String MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT";
+    @UsedForTesting
+    public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
 
     private long mNativeDict;
     private final Locale mLocale;
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index ffeb927..47891c6 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -127,7 +127,7 @@
             if (DEBUG) {
                 Log.d(TAG, "loadAccountVocabulary: " + word);
             }
-            super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
+            super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, 0 /* shortcutFreq */,
                     false /* isNotAWord */);
         }
     }
@@ -213,7 +213,7 @@
                         Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord);
                     }
                     super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
-                            false /* isNotAWord */);
+                            0 /* shortcutFreq */, false /* isNotAWord */);
                     if (!TextUtils.isEmpty(prevWord)) {
                         if (mUseFirstLastBigrams) {
                             super.addBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
index 84abfa6..3df2a2b 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -62,13 +62,13 @@
     // considering performance regression.
     @Override
     public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
-            final boolean isNotAWord) {
+            final int shortcutFreq, final boolean isNotAWord) {
         if (shortcutTarget == null) {
             mFusionDictionary.add(word, frequency, null, isNotAWord);
         } else {
             // TODO: Do this in the subclass, with this class taking an arraylist.
             final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
-            shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
+            shortcutTargets.add(new WeightedString(shortcutTarget, shortcutFreq));
             mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c79a4ff..eb8650e 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -261,10 +261,16 @@
 
     /**
      * Adds a word unigram to the dictionary. Used for loading a dictionary.
+     * @param word The word to add.
+     * @param shortcutTarget A shortcut target for this word, or null if none.
+     * @param frequency The frequency for this unigram.
+     * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
+     *   if shortcutTarget is null.
+     * @param isNotAWord true if this is not a word, i.e. shortcut only.
      */
     protected void addWord(final String word, final String shortcutTarget,
-            final int frequency, final boolean isNotAWord) {
-        mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
+            final int frequency, final int shortcutFreq, final boolean isNotAWord) {
+        mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq, isNotAWord);
     }
 
     /**
@@ -313,7 +319,7 @@
      * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
      */
     protected void addWordDynamically(final String word, final String shortcutTarget,
-            final int frequency, final boolean isNotAWord) {
+            final int frequency, final int shortcutFreq, final boolean isNotAWord) {
         if (!mIsUpdatable) {
             Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename);
             return;
@@ -326,7 +332,8 @@
                     mBinaryDictionary.addUnigramWord(word, frequency);
                 } else {
                     // TODO: Remove.
-                    mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
+                    mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, shortcutFreq,
+                            isNotAWord);
                 }
             }
         });
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index d491f98..95c9bca 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -156,15 +156,36 @@
         return Constants.DICTIONARY_MAX_WORD_LENGTH;
     }
 
-    public void addWord(final String word, final String shortcutTarget, final int frequency) {
+    /**
+     * Add a word with an optional shortcut to the dictionary.
+     * @param word The word to add.
+     * @param shortcutTarget A shortcut target for this word, or null if none.
+     * @param frequency The frequency for this unigram.
+     * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
+     *   if shortcutTarget is null.
+     */
+    public void addWord(final String word, final String shortcutTarget, final int frequency,
+            final int shortcutFreq) {
         if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
             return;
         }
-        addWordRec(mRoots, word, 0, shortcutTarget, frequency, null);
+        addWordRec(mRoots, word, 0, shortcutTarget, frequency, shortcutFreq, null);
     }
 
+    /**
+     * Add a word, recursively searching for its correct place in the trie tree.
+     * @param children The node to recursively search for addition. Initially, the root of the tree.
+     * @param word The word to add.
+     * @param depth The current depth in the tree.
+     * @param shortcutTarget A shortcut target for this word, or null if none.
+     * @param frequency The frequency for this unigram.
+     * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
+     *   if shortcutTarget is null.
+     * @param parentNode The parent node, for up linking. Initially null, as the root has no parent.
+     */
     private void addWordRec(final NodeArray children, final String word, final int depth,
-            final String shortcutTarget, final int frequency, final Node parentNode) {
+            final String shortcutTarget, final int frequency, final int shortcutFreq,
+            final Node parentNode) {
         final int wordLength = word.length();
         if (wordLength <= depth) return;
         final char c = word.charAt(depth);
@@ -204,7 +225,8 @@
         if (childNode.mChildren == null) {
             childNode.mChildren = new NodeArray();
         }
-        addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, childNode);
+        addWordRec(childNode.mChildren, word, depth + 1, shortcutTarget, frequency, shortcutFreq,
+                childNode);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d51c63d..4d7e43e 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -808,6 +808,7 @@
     @SuppressWarnings("deprecation")
     private void onStartInputViewInternal(final EditorInfo editorInfo, final boolean restarting) {
         super.onStartInputView(editorInfo, restarting);
+        mRichImm.clearSubtypeCaches();
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
         // If we are starting input in a different text field from before, we'll have to reload
@@ -903,12 +904,17 @@
         // Sometimes, while rotating, for some reason the framework tells the app we are not
         // connected to it and that means we can't refresh the cache. In this case, schedule a
         // refresh later.
+        final boolean canReachInputConnection;
         if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(editorInfo.initialSelStart,
                 false /* shouldFinishComposition */)) {
             // We try resetting the caches up to 5 times before giving up.
             mHandler.postResetCaches(isDifferentTextField, 5 /* remainingTries */);
+            canReachInputConnection = false;
         } else {
-            if (isDifferentTextField) mHandler.postResumeSuggestions();
+            if (isDifferentTextField) {
+                mHandler.postResumeSuggestions();
+            }
+            canReachInputConnection = true;
         }
 
         if (isDifferentTextField) {
@@ -920,7 +926,11 @@
                 suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
             }
 
-            switcher.loadKeyboard(editorInfo, currentSettingsValues);
+            if (canReachInputConnection) {
+                // If we can't reach the input connection, we don't want to call loadKeyboard yet.
+                // It will be done in #retryResetCaches.
+                switcher.loadKeyboard(editorInfo, currentSettingsValues);
+            }
         } else if (restarting) {
             // TODO: Come up with a more comprehensive way to reset the keyboard layout when
             // a keyboard layout set doesn't get reloaded in this method.
@@ -1049,7 +1059,6 @@
         // Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
         if (mWordComposer.isComposingWord()) mConnection.finishComposingText();
         resetComposingState(true /* alsoResetLastComposedWord */);
-        mRichImm.clearSubtypeCaches();
         // Notify ResearchLogger
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart,
@@ -1402,14 +1411,15 @@
     // Called from the KeyboardSwitcher which needs to know auto caps state to display
     // the right layout.
     public int getCurrentAutoCapsState() {
-        if (!mSettings.getCurrent().mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
+        final SettingsValues currentSettingsValues = mSettings.getCurrent();
+        if (!currentSettingsValues.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF;
 
         final EditorInfo ei = getCurrentInputEditorInfo();
         if (ei == null) return Constants.TextUtils.CAP_MODE_OFF;
         final int inputType = ei.inputType;
         // Warning: this depends on mSpaceState, which may not be the most current value. If
         // mSpaceState gets updated later, whoever called this may need to be told about it.
-        return mConnection.getCursorCapsMode(inputType, mSubtypeSwitcher.getCurrentSubtypeLocale(),
+        return mConnection.getCursorCapsMode(inputType, currentSettingsValues,
                 SPACE_STATE_PHANTOM == mSpaceState);
     }
 
@@ -1450,9 +1460,9 @@
     }
 
     private boolean maybeDoubleSpacePeriod() {
-        final SettingsValues settingsValues = mSettings.getCurrent();
-        if (!settingsValues.mCorrectionEnabled) return false;
-        if (!settingsValues.mUseDoubleSpacePeriod) return false;
+        final SettingsValues currentSettingsValues = mSettings.getCurrent();
+        if (!currentSettingsValues.mCorrectionEnabled) return false;
+        if (!currentSettingsValues.mUseDoubleSpacePeriod) return false;
         if (!mHandler.isAcceptingDoubleSpacePeriod()) return false;
         // We only do this when we see two spaces and an accepted code point before the cursor.
         // The code point may be a surrogate pair but the two spaces may not, so we need 4 chars.
@@ -1471,7 +1481,9 @@
         if (canBeFollowedByDoubleSpacePeriod(firstCodePoint)) {
             mHandler.cancelDoubleSpacePeriodTimer();
             mConnection.deleteSurroundingText(2, 0);
-            final String textToInsert = ". ";
+            final String textToInsert = new String(
+                    new int[] { currentSettingsValues.mSentenceSeparator, Constants.CODE_SPACE },
+                    0, 2);
             mConnection.commitText(textToInsert, 1);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
@@ -2955,11 +2967,13 @@
         if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(mLastSelectionStart, false)) {
             if (0 < remainingTries) {
                 mHandler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
+                return;
             }
-            return;
+            // If remainingTries is 0, we should stop waiting for new tries, but it's still
+            // better to load the keyboard (less things will be broken).
         }
         tryFixLyingCursorPosition();
-        mKeyboardSwitcher.updateShiftState();
+        mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent());
         if (tryResumeSuggestions) mHandler.postResumeSuggestions();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8580a6e..e43cab5 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -245,11 +245,11 @@
      * American English, it's just the most common set of rules for English).
      *
      * @param inputType a mask of the caps modes to test for.
-     * @param locale what language should be considered.
+     * @param settingsValues the values of the settings to use for locale and separators.
      * @param hasSpaceBefore if we should consider there should be a space after the string.
      * @return the caps modes that should be on as a set of bits
      */
-    public int getCursorCapsMode(final int inputType, final Locale locale,
+    public int getCursorCapsMode(final int inputType, final SettingsValues settingsValues,
             final boolean hasSpaceBefore) {
         mIC = mParent.getCurrentInputConnection();
         if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF;
@@ -277,8 +277,8 @@
         }
         // This never calls InputConnection#getCapsMode - in fact, it's a static method that
         // never blocks or initiates IPC.
-        return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType, locale,
-                hasSpaceBefore);
+        return CapsModeUtils.getCapsMode(mCommittedTextBeforeComposingText, inputType,
+                settingsValues, hasSpaceBefore);
     }
 
     public int getCodePointBeforeCursor() {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 9fd1f53..c270d47 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -286,14 +286,16 @@
         // the word *would* have been auto-corrected.
         if (!isCorrectionEnabled || !allowsToBeAutoCorrected || !wordComposer.isComposingWord()
                 || suggestionsSet.isEmpty() || wordComposer.hasDigits()
-                || wordComposer.isMostlyCaps() || wordComposer.isResumed()
-                || !hasMainDictionary()) {
+                || wordComposer.isMostlyCaps() || wordComposer.isResumed() || !hasMainDictionary()
+                || SuggestedWordInfo.KIND_SHORTCUT == suggestionsSet.first().mKind) {
             // If we don't have a main dictionary, we never want to auto-correct. The reason for
             // this is, the user may have a contact whose name happens to match a valid word in
             // their language, and it will unexpectedly auto-correct. For example, if the user
             // types in English with no dictionary and has a "Will" in their contact list, "will"
             // would always auto-correct to "Will" which is unwanted. Hence, no main dict => no
             // auto-correct.
+            // Also, shortcuts should never auto-correct unless they are whitelist entries.
+            // TODO: we may want to have shortcut-only entries auto-correct in the future.
             hasAutoCorrection = false;
         } else {
             hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 864a173..15b3d8d 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -47,6 +47,9 @@
     private static final String USER_DICTIONARY_ALL_LANGUAGES = "";
     private static final int HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY = 250;
     private static final int LATINIME_DEFAULT_USER_DICTIONARY_FREQUENCY = 160;
+    // Shortcut frequency is 0~15, with 15 = whitelist. We don't want user dictionary entries
+    // to auto-correct, so we set this to the highest frequency that won't, i.e. 14.
+    private static final int USER_DICT_SHORTCUT_FREQUENCY = 14;
 
     // TODO: use Words.SHORTCUT when we target JellyBean or above
     final static String SHORTCUT = "shortcut";
@@ -243,10 +246,12 @@
                 final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency);
                 // Safeguard against adding really long words.
                 if (word.length() < MAX_WORD_LENGTH) {
-                    super.addWord(word, null, adjustedFrequency, false /* isNotAWord */);
+                    super.addWord(word, null, adjustedFrequency, 0 /* shortcutFreq */,
+                            false /* isNotAWord */);
                 }
                 if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
-                    super.addWord(shortcut, word, adjustedFrequency, true /* isNotAWord */);
+                    super.addWord(shortcut, word, adjustedFrequency, USER_DICT_SHORTCUT_FREQUENCY,
+                            true /* isNotAWord */);
                 }
                 cursor.moveToNext();
             }
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index be653fe..3bb218b 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -367,10 +367,11 @@
      * Helper method to convert a String to an int array.
      */
     static int[] getCodePoints(final String word) {
-        // TODO: this is a copy-paste of the contents of StringUtils.toCodePointArray,
+        // TODO: this is a copy-paste of the old contents of StringUtils.toCodePointArray,
         // which is not visible from the makedict package. Factor this code.
+        final int length = word.length();
+        if (length <= 0) return new int[] {};
         final char[] characters = word.toCharArray();
-        final int length = characters.length;
         final int[] codePoints = new int[Character.codePointCount(characters, 0, length)];
         int codePoint = Character.codePointAt(characters, 0);
         int dsti = 0;
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index c8b62b6..a1e3600 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -138,7 +138,7 @@
         final int frequency = ENABLE_BINARY_DICTIONARY_DYNAMIC_UPDATE ?
                 (isValid ? FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS) :
                         FREQUENCY_FOR_TYPED;
-        addWordDynamically(word1, null /* the "shortcut" parameter is null */, frequency,
+        addWordDynamically(word1, null /* shortcutTarget */, frequency, 0 /* shortcutFreq */,
                 false /* isNotAWord */);
         // Do not insert a word as a bigram of itself
         if (word1.equals(word0)) {
@@ -171,11 +171,11 @@
         final OnAddWordListener listener = new OnAddWordListener() {
             @Override
             public void setUnigram(final String word, final String shortcutTarget,
-                    final int frequency) {
+                    final int frequency, final int shortcutFreq) {
                 if (DBG_SAVE_RESTORE) {
                     Log.d(TAG, "load unigram: " + word + "," + frequency);
                 }
-                addWord(word, shortcutTarget, frequency, false /* isNotAWord */);
+                addWord(word, shortcutTarget, frequency, shortcutFreq, false /* isNotAWord */);
                 ++profTotalCount[0];
             }
 
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
index 039b253..6f152bb 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPersonalizationDictionaryWriter.java
@@ -75,15 +75,21 @@
     /**
      * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
      * are done to update the binary dictionary.
+     * @param word The word to add.
+     * @param shortcutTarget A shortcut target for this word, or null if none.
+     * @param frequency The frequency for this unigram.
+     * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist). Ignored
+     *   if shortcutTarget is null.
+     * @param isNotAWord true if this is not a word, i.e. shortcut only.
      */
     @Override
     public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
-            final boolean isNotAWord) {
+            final int shortcutFreq, final boolean isNotAWord) {
         if (mBigramList.size() > mMaxHistoryBigrams * 2) {
             // Too many entries: just stop adding new vocabulary and wait next refresh.
             return;
         }
-        mExpandableDictionary.addWord(word, shortcutTarget, frequency);
+        mExpandableDictionary.addWord(word, shortcutTarget, frequency, shortcutFreq);
         mBigramList.addBigram(null, word, (byte)frequency);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 2abcdc7..f331c78 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -24,6 +24,7 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.InputAttributes;
 import com.android.inputmethod.latin.R;
@@ -57,6 +58,7 @@
     public final int[] mWordConnectors;
     public final SuggestedWords mSuggestPuncList;
     public final String mWordSeparators;
+    public final int mSentenceSeparator;
     public final CharSequence mHintToSaveText;
     public final boolean mCurrentLanguageHasSpaces;
 
@@ -120,6 +122,7 @@
                 R.string.suggested_punctuations));
         mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
         mWordSeparators = res.getString(R.string.symbols_word_separators);
+        mSentenceSeparator = res.getInteger(R.integer.sentence_separator);
         mHintToSaveText = res.getText(R.string.hint_add_to_dictionary);
         mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces);
 
@@ -187,6 +190,7 @@
         Arrays.sort(mSymbolsFollowedBySpace);
         mWordConnectors = new int[] { '\'', '-' };
         Arrays.sort(mWordConnectors);
+        mSentenceSeparator = Constants.CODE_PERIOD;
         final String[] suggestPuncsSpec = new String[] { "!", "?", ",", ":", ";" };
         mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
         mWordSeparators = "&\t \n()[]{}*&<>+=|.,;:!?/_\"";
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index eb6d7c1..503b18b 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -204,10 +204,20 @@
         return AndroidSpellCheckerSessionFactory.newInstance(this);
     }
 
-    public static SuggestionsInfo getNotInDictEmptySuggestions() {
-        return new SuggestionsInfo(0, EMPTY_STRING_ARRAY);
+    /**
+     * Returns an empty SuggestionsInfo with flags signaling the word is not in the dictionary.
+     * @param reportAsTypo whether this should include the flag LOOKS_LIKE_TYPO, for red underline.
+     * @return the empty SuggestionsInfo with the appropriate flags set.
+     */
+    public static SuggestionsInfo getNotInDictEmptySuggestions(final boolean reportAsTypo) {
+        return new SuggestionsInfo(reportAsTypo ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0,
+                EMPTY_STRING_ARRAY);
     }
 
+    /**
+     * Returns an empty suggestionInfo with flags signaling the word is in the dictionary.
+     * @return the empty SuggestionsInfo with the appropriate flags set.
+     */
     public static SuggestionsInfo getInDictEmptySuggestions() {
         return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY,
                 EMPTY_STRING_ARRAY);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 69f9a46..d6e5b75 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -161,6 +161,12 @@
         }
     }
 
+    private static final int CHECKABILITY_CHECKABLE = 0;
+    private static final int CHECKABILITY_TOO_MANY_NON_LETTERS = 1;
+    private static final int CHECKABILITY_CONTAINS_PERIOD = 2;
+    private static final int CHECKABILITY_EMAIL_OR_URL = 3;
+    private static final int CHECKABILITY_FIRST_LETTER_UNCHECKABLE = 4;
+    private static final int CHECKABILITY_TOO_SHORT = 5;
     /**
      * Finds out whether a particular string should be filtered out of spell checking.
      *
@@ -171,10 +177,10 @@
      *
      * @param text the string to evaluate.
      * @param script the identifier for the script this spell checker recognizes
-     * @return true if we should filter this text out, false otherwise
+     * @return one of the FILTER_OUT_* constants above.
      */
-    private static boolean shouldFilterOut(final String text, final int script) {
-        if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
+    private static int getCheckabilityInScript(final String text, final int script) {
+        if (TextUtils.isEmpty(text) || text.length() <= 1) return CHECKABILITY_TOO_SHORT;
 
         // TODO: check if an equivalent processing can't be done more quickly with a
         // compiled regexp.
@@ -182,7 +188,7 @@
         final int firstCodePoint = text.codePointAt(0);
         // Filter out words that don't start with a letter or an apostrophe
         if (!isLetterCheckableByLanguage(firstCodePoint, script)
-                && '\'' != firstCodePoint) return true;
+                && '\'' != firstCodePoint) return CHECKABILITY_FIRST_LETTER_UNCHECKABLE;
 
         // Filter contents
         final int length = text.length();
@@ -193,13 +199,21 @@
             // Any word containing a SLASH is probably either an ad-hoc combination of two
             // words or a URI - in either case we don't want to spell check that
             if (Constants.CODE_COMMERCIAL_AT == codePoint || Constants.CODE_SLASH == codePoint) {
-                return true;
+                return CHECKABILITY_EMAIL_OR_URL;
+            }
+            // If the string contains a period, native returns strange suggestions (it seems
+            // to return suggestions for everything up to the period only and to ignore the
+            // rest), so we suppress lookup if there is a period.
+            // TODO: investigate why native returns these suggestions and remove this code.
+            if (Constants.CODE_PERIOD == codePoint) {
+                return CHECKABILITY_CONTAINS_PERIOD;
             }
             if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
         }
         // Guestimate heuristic: perform spell checking if at least 3/4 of the characters
         // in this word are letters
-        return (letterCount * 4 < length * 3);
+        return (letterCount * 4 < length * 3)
+                ? CHECKABILITY_TOO_MANY_NON_LETTERS : CHECKABILITY_CHECKABLE;
     }
 
     /**
@@ -256,16 +270,20 @@
                         cachedSuggestionsParams.mFlags, cachedSuggestionsParams.mSuggestions);
             }
 
-            if (shouldFilterOut(inText, mScript)) {
+            final int checkability = getCheckabilityInScript(inText, mScript);
+            if (CHECKABILITY_CHECKABLE != checkability) {
                 DictAndKeyboard dictInfo = null;
                 try {
                     dictInfo = mDictionaryPool.pollWithDefaultTimeout();
                     if (!DictionaryPool.isAValidDictionary(dictInfo)) {
-                        return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
+                        return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
+                                false /* reportAsTypo */);
                     }
                     return dictInfo.mDictionary.isValidWord(inText)
                             ? AndroidSpellCheckerService.getInDictEmptySuggestions()
-                            : AndroidSpellCheckerService.getNotInDictEmptySuggestions();
+                            : AndroidSpellCheckerService.getNotInDictEmptySuggestions(
+                                    CHECKABILITY_CONTAINS_PERIOD == checkability
+                                    /* reportAsTypo */);
                 } finally {
                     if (null != dictInfo) {
                         if (!mDictionaryPool.offer(dictInfo)) {
@@ -290,7 +308,8 @@
             try {
                 dictInfo = mDictionaryPool.pollWithDefaultTimeout();
                 if (!DictionaryPool.isAValidDictionary(dictInfo)) {
-                    return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
+                    return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
+                            false /* reportAsTypo */);
                 }
                 final WordComposer composer = new WordComposer();
                 final int length = text.length();
@@ -351,7 +370,8 @@
                 throw e;
             } else {
                 Log.e(TAG, "Exception while spellcheking", e);
-                return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
+                return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
+                        false /* reportAsTypo */);
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 60b24d5..3d4404a 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -21,6 +21,7 @@
 
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.settings.SettingsValues;
 
 import java.util.Locale;
 
@@ -60,11 +61,6 @@
                 || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode;
     }
 
-    private static boolean isPeriod(final int codePoint) {
-        // TODO: make this a resource.
-        return codePoint == Constants.CODE_PERIOD || codePoint == Constants.CODE_ARMENIAN_PERIOD;
-    }
-
     /**
      * Determine what caps mode should be in effect at the current offset in
      * the text. Only the mode bits set in <var>reqModes</var> will be
@@ -78,7 +74,7 @@
      * @param reqModes The modes to be checked: may be any combination of
      * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
      * {@link TextUtils#CAP_MODE_SENTENCES}.
-     * @param locale The locale to consider for capitalization rules
+     * @param settingsValues The current settings values.
      * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
      *
      * @return Returns the actual capitalization modes that can be in effect
@@ -86,8 +82,8 @@
      * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
      * {@link TextUtils#CAP_MODE_SENTENCES}.
      */
-    public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale,
-            final boolean hasSpaceBefore) {
+    public static int getCapsMode(final CharSequence cs, final int reqModes,
+            final SettingsValues settingsValues, final boolean hasSpaceBefore) {
         // Quick description of what we want to do:
         // CAP_MODE_CHARACTERS is always on.
         // CAP_MODE_WORDS is on if there is some whitespace before the cursor.
@@ -172,7 +168,7 @@
         // mark as the exact thing quoted and handling the surrounding punctuation independently,
         // e.g. <<Did he say, "let's go home"?>>
         // Hence, specifically for English, we treat this special case here.
-        if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
+        if (Locale.ENGLISH.getLanguage().equals(settingsValues.mLocale.getLanguage())) {
             for (; j > 0; j--) {
                 // Here we look to go over any closing punctuation. This is because in dominant
                 // variants of English, the final period is placed within double quotes and maybe
@@ -195,7 +191,7 @@
         if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
             return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
         }
-        if (!isPeriod(c) || j <= 0) {
+        if (settingsValues.mSentenceSeparator != c || j <= 0) {
             return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
         }
 
@@ -245,7 +241,7 @@
             case WORD:
                 if (Character.isLetter(c)) {
                     state = WORD;
-                } else if (isPeriod(c)) {
+                } else if (settingsValues.mSentenceSeparator == c) {
                     state = PERIOD;
                 } else {
                     return caps;
@@ -261,7 +257,7 @@
             case LETTER:
                 if (Character.isLetter(c)) {
                     state = LETTER;
-                } else if (isPeriod(c)) {
+                } else if (settingsValues.mSentenceSeparator == c) {
                     state = PERIOD;
                 } else {
                     return noCaps;
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index ea32a74..635afe7 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -49,7 +49,16 @@
     private static final String LAST_UPDATED_TIME_KEY = "date";
 
     public interface OnAddWordListener {
-        public void setUnigram(final String word, final String shortcutTarget, final int frequency);
+        /**
+         * Callback to be notified when a word is added to the dictionary.
+         * @param word The added word.
+         * @param shortcutTarget A shortcut target for this word, or null if none.
+         * @param frequency The frequency for this word.
+         * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist).
+         *   Unspecified if shortcutTarget is null - do not rely on its value.
+         */
+        public void setUnigram(final String word, final String shortcutTarget, final int frequency,
+                final int shortcutFreq);
         public void setBigram(final String word1, final String word2, final int frequency);
     }
 
@@ -153,7 +162,7 @@
         for (Entry<Integer, String> entry : unigrams.entrySet()) {
             final String word1 = entry.getValue();
             final int unigramFrequency = frequencies.get(entry.getKey());
-            to.setUnigram(word1, null, unigramFrequency);
+            to.setUnigram(word1, null /* shortcutTarget */, unigramFrequency, 0 /* shortcutFreq */);
             final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey());
             if (attrList != null) {
                 for (final PendingAttribute attr : attrList) {
diff --git a/native/jni/src/suggest/core/dictionary/shortcut_utils.h b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
index 461d7b4..9ccef02 100644
--- a/native/jni/src/suggest/core/dictionary/shortcut_utils.h
+++ b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
@@ -44,7 +44,7 @@
                 shortcutScore = finalScore;
                 // Protection against int underflow
                 shortcutScore = max(S_INT_MIN + 1, shortcutScore) - 1;
-                kind = Dictionary::KIND_CORRECTION;
+                kind = Dictionary::KIND_SHORTCUT;
             }
             outputTypes[outputWordIndex] = kind;
             frequencies[outputWordIndex] = shortcutScore;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
index a17a0ac..5724c5d 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
@@ -39,7 +39,7 @@
             return false;
         }
         if (!ForgettingCurveUtils::isValidEncodedProbability(newProbability)) {
-            isUselessPtNode = false;
+            isUselessPtNode = true;
         }
     }
     if (mChildrenValue > 0) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
index 3ca2f2a..9755120 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
@@ -60,6 +60,7 @@
 
         bool onDescend(const int ptNodeArrayPos) {
             mValueStack.push_back(0);
+            mChildrenValue = 0;
             return true;
         }
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
index 31e3fb4..a8ea69f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
@@ -37,6 +37,8 @@
 // BinaryDictionaryDecayingTests.
 const char *const DynamicPatriciaTriePolicy::UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
 const char *const DynamicPatriciaTriePolicy::BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
 const char *const DynamicPatriciaTriePolicy::SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY =
         "SET_NEEDS_TO_DECAY_FOR_TESTING";
 const int DynamicPatriciaTriePolicy::MAX_DICT_EXTENDED_REGION_SIZE = 1024 * 1024;
@@ -355,6 +357,14 @@
         snprintf(outResult, maxResultLength, "%d", mUnigramCount);
     } else if (strncmp(query, BIGRAM_COUNT_QUERY, maxResultLength) == 0) {
         snprintf(outResult, maxResultLength, "%d", mBigramCount);
+    } else if (strncmp(query, MAX_UNIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d",
+                mHeaderPolicy.isDecayingDict() ? ForgettingCurveUtils::MAX_UNIGRAM_COUNT :
+                        static_cast<int>(DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE));
+    } else if (strncmp(query, MAX_BIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d",
+                mHeaderPolicy.isDecayingDict() ? ForgettingCurveUtils::MAX_BIGRAM_COUNT :
+                        static_cast<int>(DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE));
     } else if (strncmp(query, SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY, maxResultLength) == 0) {
         mNeedsToDecayForTesting = true;
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
index 903f65e..be97ee1 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
@@ -102,6 +102,8 @@
 
     static const char *const UNIGRAM_COUNT_QUERY;
     static const char *const BIGRAM_COUNT_QUERY;
+    static const char *const MAX_UNIGRAM_COUNT_QUERY;
+    static const char *const MAX_BIGRAM_COUNT_QUERY;
     static const char *const SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY;
     static const int MAX_DICT_EXTENDED_REGION_SIZE;
     static const int MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
index 601ee66..f108c21 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
@@ -93,6 +93,12 @@
     if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
         return false;
     }
+    if (isEnd()) {
+        // Empty dictionary. Needs to notify the listener of the tail of empty PtNode array.
+        if (!listener->onReadingPtNodeArrayTail()) {
+            return false;
+        }
+    }
     pushReadingStateToStack();
     while (!isEnd()) {
         if (alreadyVisitedAllPtNodesInArray) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
index 512a4d8..a71c069 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
@@ -279,7 +279,9 @@
         } else {
             mReadingState = mReadingStateStack.back();
             mReadingStateStack.pop_back();
-            fetchPtNodeInfo();
+            if (!isEnd()) {
+                fetchPtNodeInfo();
+            }
         }
     }
 };
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
index 067c8ec..052558b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
@@ -240,7 +240,8 @@
             int parentOffsetFieldPos = nodeReader->getHeadPos()
                     + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
             if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
-                    mBuffer, movedPos, nodeReader->getHeadPos(), &parentOffsetFieldPos)) {
+                    mBuffer, bigramLinkedNodePos, nodeReader->getHeadPos(),
+                    &parentOffsetFieldPos)) {
                 // Parent offset cannot be written because of a bug or a broken dictionary; thus,
                 // we give up to update dictionary.
                 return false;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
index 19ca354..1632fd0 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp
@@ -93,8 +93,7 @@
     for (int i = 0; i < decayIterationCount; ++i) {
         const float currentRate = static_cast<float>(currentEncodedProbability)
                 / static_cast<float>(MAX_ENCODED_PROBABILITY);
-        const float thresholdToDecay = MIN_PROBABILITY_TO_DECAY
-                + (1.0f - MIN_PROBABILITY_TO_DECAY) * currentRate;
+        const float thresholdToDecay = (1.0f - MIN_PROBABILITY_TO_DECAY) * currentRate;
         const float randValue = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
         if (thresholdToDecay < randValue) {
             currentEncodedProbability = max(currentEncodedProbability - ENCODED_PROBABILITY_STEP,
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index ded8eaa..cd5384e 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -18,14 +18,18 @@
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Pair;
 
+import com.android.inputmethod.latin.makedict.CodePointUtils;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Random;
 
 @LargeTest
 public class BinaryDictionaryDecayingTests extends AndroidTestCase {
@@ -121,11 +125,16 @@
         binaryDictionary.addBigramWords("a", "c", DUMMY_PROBABILITY);
         assertTrue(binaryDictionary.isValidBigram("a", "c"));
 
+        // Add bigrams of not valid unigrams.
+        binaryDictionary.addBigramWords("x", "y", Dictionary.NOT_A_PROBABILITY);
+        assertFalse(binaryDictionary.isValidBigram("x", "y"));
+        binaryDictionary.addBigramWords("x", "y", DUMMY_PROBABILITY);
+        assertFalse(binaryDictionary.isValidBigram("x", "y"));
+
         binaryDictionary.close();
         dictFile.delete();
     }
 
-    // TODO: Add large tests.
     public void testDecayingProbability() {
         File dictFile = null;
         try {
@@ -179,4 +188,121 @@
         binaryDictionary.close();
         dictFile.delete();
     }
+
+    public void testAddManyUnigramsToDecayingDict() {
+        final int unigramCount = 30000;
+        final int unigramTypedCount = 100000;
+        final int codePointSetSize = 50;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        final ArrayList<String> words = new ArrayList<String>();
+
+        for (int i = 0; i < unigramCount; i++) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            words.add(word);
+        }
+
+        final int maxUnigramCount = Integer.parseInt(
+                binaryDictionary.getPropertyForTests(BinaryDictionary.MAX_UNIGRAM_COUNT_QUERY));
+        for (int i = 0; i < unigramTypedCount; i++) {
+            final String word = words.get(random.nextInt(words.size()));
+            binaryDictionary.addUnigramWord(word, DUMMY_PROBABILITY);
+
+            if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                final int unigramCountBeforeGC =
+                        Integer.parseInt(binaryDictionary.getPropertyForTests(
+                                BinaryDictionary.UNIGRAM_COUNT_QUERY));
+                while (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                    binaryDictionary.flushWithGC();
+                }
+                final int unigramCountAfterGC =
+                        Integer.parseInt(binaryDictionary.getPropertyForTests(
+                                BinaryDictionary.UNIGRAM_COUNT_QUERY));
+                assertTrue(unigramCountBeforeGC > unigramCountAfterGC);
+            }
+        }
+
+        assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests(
+                BinaryDictionary.UNIGRAM_COUNT_QUERY)) > 0);
+        assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests(
+                BinaryDictionary.UNIGRAM_COUNT_QUERY)) <= maxUnigramCount);
+    }
+
+    public void testAddManyBigramsToDecayingDict() {
+        final int unigramCount = 5000;
+        final int bigramCount = 30000;
+        final int bigramTypedCount = 100000;
+        final int codePointSetSize = 50;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+
+        File dictFile = null;
+        try {
+            dictFile = createEmptyDictionaryAndGetFile("TestBinaryDictionary");
+        } catch (IOException e) {
+            fail("IOException while writing an initial dictionary : " + e);
+        }
+        BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile.getAbsolutePath(),
+                0 /* offset */, dictFile.length(), true /* useFullEditDistance */,
+                Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */);
+
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        final ArrayList<String> words = new ArrayList<String>();
+        final ArrayList<Pair<String, String>> bigrams = new ArrayList<Pair<String, String>>();
+
+        for (int i = 0; i < unigramCount; ++i) {
+            final String word = CodePointUtils.generateWord(random, codePointSet);
+            words.add(word);
+        }
+        for (int i = 0; i < bigramCount; ++i) {
+            final int word0Index = random.nextInt(words.size());
+            int word1Index = random.nextInt(words.size() - 1);
+            if (word1Index >= word0Index) {
+                word1Index += 1;
+            }
+            final String word0 = words.get(word0Index);
+            final String word1 = words.get(word1Index);
+            final Pair<String, String> bigram = new Pair<String, String>(word0, word1);
+            bigrams.add(bigram);
+        }
+
+        final int maxBigramCount = Integer.parseInt(
+                binaryDictionary.getPropertyForTests(BinaryDictionary.MAX_BIGRAM_COUNT_QUERY));
+        for (int i = 0; i < bigramTypedCount; ++i) {
+            final Pair<String, String> bigram = bigrams.get(random.nextInt(bigrams.size()));
+            binaryDictionary.addUnigramWord(bigram.first, DUMMY_PROBABILITY);
+            binaryDictionary.addUnigramWord(bigram.second, DUMMY_PROBABILITY);
+            binaryDictionary.addBigramWords(bigram.first, bigram.second, DUMMY_PROBABILITY);
+
+            if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                final int bigramCountBeforeGC =
+                        Integer.parseInt(binaryDictionary.getPropertyForTests(
+                                BinaryDictionary.BIGRAM_COUNT_QUERY));
+                while (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
+                    binaryDictionary.flushWithGC();
+                }
+                final int bigramCountAfterGC =
+                        Integer.parseInt(binaryDictionary.getPropertyForTests(
+                                BinaryDictionary.BIGRAM_COUNT_QUERY));
+                assertTrue(bigramCountBeforeGC > bigramCountAfterGC);
+            }
+        }
+
+        assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests(
+                BinaryDictionary.BIGRAM_COUNT_QUERY)) > 0);
+        assertTrue(Integer.parseInt(binaryDictionary.getPropertyForTests(
+                BinaryDictionary.BIGRAM_COUNT_QUERY)) <= maxBigramCount);
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java b/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java
index ecf3af7..6aae104 100644
--- a/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/ExpandableDictionaryTests.java
@@ -26,13 +26,16 @@
 public class ExpandableDictionaryTests extends AndroidTestCase {
 
     private final static int UNIGRAM_FREQ = 50;
+    // See UserBinaryDictionary for more information about this variable.
+    // For tests, its actual value does not matter.
+    private final static int SHORTCUT_FREQ = 14;
 
     public void testAddWordAndGetWordFrequency() {
         final ExpandableDictionary dict = new ExpandableDictionary(Dictionary.TYPE_USER);
 
         // Add words
-        dict.addWord("abcde", "abcde", UNIGRAM_FREQ);
-        dict.addWord("abcef", null, UNIGRAM_FREQ + 1);
+        dict.addWord("abcde", "abcde", UNIGRAM_FREQ, SHORTCUT_FREQ);
+        dict.addWord("abcef", null, UNIGRAM_FREQ + 1, 0);
 
         // Check words
         assertFalse(dict.isValidWord("abcde"));
@@ -40,16 +43,16 @@
         assertTrue(dict.isValidWord("abcef"));
         assertEquals(UNIGRAM_FREQ+1, dict.getWordFrequency("abcef"));
 
-        dict.addWord("abc", null, UNIGRAM_FREQ + 2);
+        dict.addWord("abc", null, UNIGRAM_FREQ + 2, 0);
         assertTrue(dict.isValidWord("abc"));
         assertEquals(UNIGRAM_FREQ + 2, dict.getWordFrequency("abc"));
 
         // Add existing word with lower frequency
-        dict.addWord("abc", null, UNIGRAM_FREQ);
+        dict.addWord("abc", null, UNIGRAM_FREQ, 0);
         assertEquals(UNIGRAM_FREQ + 2, dict.getWordFrequency("abc"));
 
         // Add existing word with higher frequency
-        dict.addWord("abc", null, UNIGRAM_FREQ + 3);
+        dict.addWord("abc", null, UNIGRAM_FREQ + 3, 0);
         assertEquals(UNIGRAM_FREQ + 3, dict.getWordFrequency("abc"));
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 234bb1b..b9b52a6 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -238,12 +238,16 @@
     }
 
     protected void changeLanguage(final String locale) {
+        changeLanguageWithoutWait(locale);
+        waitForDictionaryToBeLoaded();
+    }
+
+    protected void changeLanguageWithoutWait(final String locale) {
         mEditText.mCurrentLocale = LocaleUtils.constructLocaleFromString(locale);
         SubtypeSwitcher.getInstance().forceLocale(mEditText.mCurrentLocale);
         mLatinIME.loadKeyboard();
         runMessages();
         mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
-        waitForDictionaryToBeLoaded();
     }
 
     protected void changeKeyboardLocaleAndDictLocale(final String keyboardLocale,
diff --git a/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java b/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java
new file mode 100644
index 0000000..5e98cdf
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/LatinImeStressTests.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.inputmethod.latin.makedict.CodePointUtils;
+
+import java.util.Random;
+
+@LargeTest
+public class LatinImeStressTests extends InputTestsBase {
+    public void testSwitchLanguagesAndInputLatinRandomCodePoints() {
+        final String[] locales = {"en_US", "de", "el", "es", "fi", "it", "nl", "pt", "ru"};
+        final int switchCount = 50;
+        final int maxWordCountToTypeInEachIteration = 20;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+        final int codePointSetSize = 30;
+        final int[] codePointSet = CodePointUtils.LATIN_ALPHABETS_LOWER;
+        for (int i = 0; i < switchCount; ++i) {
+            changeLanguageWithoutWait(locales[random.nextInt(locales.length)]);
+            final int wordCount = random.nextInt(maxWordCountToTypeInEachIteration);
+            for (int j = 0; j < wordCount; ++j) {
+                final String word = CodePointUtils.generateWord(random, codePointSet);
+                type(word);
+            }
+        }
+    }
+    public void testSwitchLanguagesAndInputRandamCodePoints() {
+        final String[] locales = {"en_US", "de", "el", "es", "fi", "it", "nl", "pt", "ru"};
+        final int switchCount = 50;
+        final int maxWordCountToTypeInEachIteration = 20;
+        final long seed = System.currentTimeMillis();
+        final Random random = new Random(seed);
+        final int codePointSetSize = 30;
+        final int[] codePointSet = CodePointUtils.generateCodePointSet(codePointSetSize, random);
+        for (int i = 0; i < switchCount; ++i) {
+            changeLanguageWithoutWait(locales[random.nextInt(locales.length)]);
+            final int wordCount = random.nextInt(maxWordCountToTypeInEachIteration);
+            for (int j = 0; j < wordCount; ++j) {
+                final String word = CodePointUtils.generateWord(random, codePointSet);
+                type(word);
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java b/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java
index 36b958a..a270ee7 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/CodePointUtils.java
@@ -24,6 +24,42 @@
         // This utility class is not publicly instantiable.
     }
 
+    public static final int[] LATIN_ALPHABETS_LOWER = {
+        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+        0x00E0 /* LATIN SMALL LETTER A WITH GRAVE */,
+        0x00E1 /* LATIN SMALL LETTER A WITH ACUTE */,
+        0x00E2 /* LATIN SMALL LETTER A WITH CIRCUMFLEX */,
+        0x00E3 /* LATIN SMALL LETTER A WITH TILDE */,
+        0x00E4 /* LATIN SMALL LETTER A WITH DIAERESIS */,
+        0x00E5 /* LATIN SMALL LETTER A WITH RING ABOVE */,
+        0x00E6 /* LATIN SMALL LETTER AE */,
+        0x00E7 /* LATIN SMALL LETTER C WITH CEDILLA */,
+        0x00E8 /* LATIN SMALL LETTER E WITH GRAVE */,
+        0x00E9 /* LATIN SMALL LETTER E WITH ACUTE */,
+        0x00EA /* LATIN SMALL LETTER E WITH CIRCUMFLEX */,
+        0x00EB /* LATIN SMALL LETTER E WITH DIAERESIS */,
+        0x00EC /* LATIN SMALL LETTER I WITH GRAVE */,
+        0x00ED /* LATIN SMALL LETTER I WITH ACUTE */,
+        0x00EE /* LATIN SMALL LETTER I WITH CIRCUMFLEX */,
+        0x00EF /* LATIN SMALL LETTER I WITH DIAERESIS */,
+        0x00F0 /* LATIN SMALL LETTER ETH */,
+        0x00F1 /* LATIN SMALL LETTER N WITH TILDE */,
+        0x00F2 /* LATIN SMALL LETTER O WITH GRAVE */,
+        0x00F3 /* LATIN SMALL LETTER O WITH ACUTE */,
+        0x00F4 /* LATIN SMALL LETTER O WITH CIRCUMFLEX */,
+        0x00F5 /* LATIN SMALL LETTER O WITH TILDE */,
+        0x00F6 /* LATIN SMALL LETTER O WITH DIAERESIS */,
+        0x00F7 /* LATIN SMALL LETTER O WITH STROKE */,
+        0x00F9 /* LATIN SMALL LETTER U WITH GRAVE */,
+        0x00FA /* LATIN SMALL LETTER U WITH ACUTE */,
+        0x00FB /* LATIN SMALL LETTER U WITH CIRCUMFLEX */,
+        0x00FC /* LATIN SMALL LETTER U WITH DIAERESIS */,
+        0x00FD /* LATIN SMALL LETTER Y WITH ACUTE */,
+        0x00FE /* LATIN SMALL LETTER THORN */,
+        0x00FF /* LATIN SMALL LETTER Y WITH DIAERESIS */
+    };
+
     public static int[] generateCodePointSet(final int codePointSetSize, final Random random) {
         final int[] codePointSet = new int[codePointSetSize];
         for (int i = codePointSet.length - 1; i >= 0; ) {
diff --git a/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java
index cf3bdd6..1fd5c98 100644
--- a/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin.utils;
 
+import com.android.inputmethod.latin.settings.SettingsValues;
+
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
@@ -25,64 +27,64 @@
 @SmallTest
 public class CapsModeUtilsTests extends AndroidTestCase {
     private static void onePathForCaps(final CharSequence cs, final int expectedResult,
-            final int mask, final Locale l, final boolean hasSpaceBefore) {
+            final int mask, final SettingsValues sv, final boolean hasSpaceBefore) {
         int oneTimeResult = expectedResult & mask;
         assertEquals("After >" + cs + "<", oneTimeResult,
-                CapsModeUtils.getCapsMode(cs, mask, l, hasSpaceBefore));
+                CapsModeUtils.getCapsMode(cs, mask, sv, hasSpaceBefore));
     }
 
     private static void allPathsForCaps(final CharSequence cs, final int expectedResult,
-            final Locale l, final boolean hasSpaceBefore) {
+            final SettingsValues sv, final boolean hasSpaceBefore) {
         final int c = TextUtils.CAP_MODE_CHARACTERS;
         final int w = TextUtils.CAP_MODE_WORDS;
         final int s = TextUtils.CAP_MODE_SENTENCES;
-        onePathForCaps(cs, expectedResult, c | w | s, l, hasSpaceBefore);
-        onePathForCaps(cs, expectedResult, w | s, l, hasSpaceBefore);
-        onePathForCaps(cs, expectedResult, c | s, l, hasSpaceBefore);
-        onePathForCaps(cs, expectedResult, c | w, l, hasSpaceBefore);
-        onePathForCaps(cs, expectedResult, c, l, hasSpaceBefore);
-        onePathForCaps(cs, expectedResult, w, l, hasSpaceBefore);
-        onePathForCaps(cs, expectedResult, s, l, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, c | w | s, sv, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, w | s, sv, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, c | s, sv, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, c | w, sv, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, c, sv, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, w, sv, hasSpaceBefore);
+        onePathForCaps(cs, expectedResult, s, sv, hasSpaceBefore);
     }
 
     public void testGetCapsMode() {
         final int c = TextUtils.CAP_MODE_CHARACTERS;
         final int w = TextUtils.CAP_MODE_WORDS;
         final int s = TextUtils.CAP_MODE_SENTENCES;
-        Locale l = Locale.ENGLISH;
-        allPathsForCaps("", c | w | s, l, false);
-        allPathsForCaps("Word", c, l, false);
-        allPathsForCaps("Word.", c, l, false);
-        allPathsForCaps("Word ", c | w, l, false);
-        allPathsForCaps("Word. ", c | w | s, l, false);
-        allPathsForCaps("Word..", c, l, false);
-        allPathsForCaps("Word.. ", c | w | s, l, false);
-        allPathsForCaps("Word... ", c | w | s, l, false);
-        allPathsForCaps("Word ... ", c | w | s, l, false);
-        allPathsForCaps("Word . ", c | w, l, false);
-        allPathsForCaps("In the U.S ", c | w, l, false);
-        allPathsForCaps("In the U.S. ", c | w, l, false);
-        allPathsForCaps("Some stuff (e.g. ", c | w, l, false);
-        allPathsForCaps("In the U.S.. ", c | w | s, l, false);
-        allPathsForCaps("\"Word.\" ", c | w | s, l, false);
-        allPathsForCaps("\"Word\". ", c | w | s, l, false);
-        allPathsForCaps("\"Word\" ", c | w, l, false);
+        SettingsValues sv = SettingsValues.makeDummySettingsValuesForTest(Locale.ENGLISH);
+        allPathsForCaps("", c | w | s, sv, false);
+        allPathsForCaps("Word", c, sv, false);
+        allPathsForCaps("Word.", c, sv, false);
+        allPathsForCaps("Word ", c | w, sv, false);
+        allPathsForCaps("Word. ", c | w | s, sv, false);
+        allPathsForCaps("Word..", c, sv, false);
+        allPathsForCaps("Word.. ", c | w | s, sv, false);
+        allPathsForCaps("Word... ", c | w | s, sv, false);
+        allPathsForCaps("Word ... ", c | w | s, sv, false);
+        allPathsForCaps("Word . ", c | w, sv, false);
+        allPathsForCaps("In the U.S ", c | w, sv, false);
+        allPathsForCaps("In the U.S. ", c | w, sv, false);
+        allPathsForCaps("Some stuff (e.g. ", c | w, sv, false);
+        allPathsForCaps("In the U.S.. ", c | w | s, sv, false);
+        allPathsForCaps("\"Word.\" ", c | w | s, sv, false);
+        allPathsForCaps("\"Word\". ", c | w | s, sv, false);
+        allPathsForCaps("\"Word\" ", c | w, sv, false);
 
         // Test for phantom space
-        allPathsForCaps("Word", c | w, l, true);
-        allPathsForCaps("Word.", c | w | s, l, true);
+        allPathsForCaps("Word", c | w, sv, true);
+        allPathsForCaps("Word.", c | w | s, sv, true);
 
         // Tests after some whitespace
-        allPathsForCaps("Word\n", c | w | s, l, false);
-        allPathsForCaps("Word\n", c | w | s, l, true);
-        allPathsForCaps("Word\n ", c | w | s, l, true);
-        allPathsForCaps("Word.\n", c | w | s, l, false);
-        allPathsForCaps("Word.\n", c | w | s, l, true);
-        allPathsForCaps("Word.\n ", c | w | s, l, true);
+        allPathsForCaps("Word\n", c | w | s, sv, false);
+        allPathsForCaps("Word\n", c | w | s, sv, true);
+        allPathsForCaps("Word\n ", c | w | s, sv, true);
+        allPathsForCaps("Word.\n", c | w | s, sv, false);
+        allPathsForCaps("Word.\n", c | w | s, sv, true);
+        allPathsForCaps("Word.\n ", c | w | s, sv, true);
 
-        l = Locale.FRENCH;
-        allPathsForCaps("\"Word.\" ", c | w, l, false);
-        allPathsForCaps("\"Word\". ", c | w | s, l, false);
-        allPathsForCaps("\"Word\" ", c | w, l, false);
+        sv = SettingsValues.makeDummySettingsValuesForTest(Locale.FRENCH);
+        allPathsForCaps("\"Word.\" ", c | w, sv, false);
+        allPathsForCaps("\"Word\". ", c | w | s, sv, false);
+        allPathsForCaps("\"Word\" ", c | w, sv, false);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
index 3eabe2b..1944fd3 100644
--- a/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
@@ -196,8 +196,8 @@
         final UserHistoryDictionaryBigramList resultList = new UserHistoryDictionaryBigramList();
         final OnAddWordListener listener = new OnAddWordListener() {
             @Override
-            public void setUnigram(final String word,
-                    final String shortcutTarget, final int frequency) {
+            public void setUnigram(final String word, final String shortcutTarget,
+                    final int frequency, final int shortcutFreq) {
                 Log.d(TAG, "in: setUnigram: " + word + "," + frequency);
                 resultList.addBigram(null, word, (byte)frequency);
             }
@@ -220,8 +220,8 @@
         final UserHistoryDictionaryBigramList resultList2 = new UserHistoryDictionaryBigramList();
         final OnAddWordListener listener2 = new OnAddWordListener() {
             @Override
-            public void setUnigram(final String word,
-                    final String shortcutTarget, final int frequency) {
+            public void setUnigram(final String word, final String shortcutTarget,
+                    final int frequency, final int shortcutFreq) {
                 Log.d(TAG, "in: setUnigram: " + word + "," + frequency);
                 resultList2.addBigram(null, word, (byte)frequency);
             }