merge in master-release history after reset to 17f326b7458c2bde2569e283a96e703755485328
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_active_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_active_lxx_dark.9.png
new file mode 100644
index 0000000..70e09f9
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_key_active_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png
new file mode 100644
index 0000000..9c44672
--- /dev/null
+++ b/java/res/drawable-hdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_off_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
index 44308bf..c0fee73 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_normal_on_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
index 674783d..591292c 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
index 96b625b..10d91a2 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png b/java/res/drawable-hdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
index 20e53c2..6a8c62f 100644
--- a/java/res/drawable-hdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
+++ b/java/res/drawable-hdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_active_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_active_lxx_dark.9.png
new file mode 100644
index 0000000..2f0c61a
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_active_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png
new file mode 100644
index 0000000..f56b978
--- /dev/null
+++ b/java/res/drawable-mdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_off_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
index 837df83..3ce13cc 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_normal_on_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
index 9772652..65507ee 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
index d213633..724e142 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png b/java/res/drawable-mdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
index 6d20c54..264b65b 100644
--- a/java/res/drawable-mdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
+++ b/java/res/drawable-mdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_active_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_active_lxx_dark.9.png
new file mode 100644
index 0000000..be28383
--- /dev/null
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_active_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png
new file mode 100644
index 0000000..4020883
--- /dev/null
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
index eeb447c..70270e2 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
index 624ba8c..31f9e02 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
index 2bc16cf..97ef98d 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png b/java/res/drawable-xhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
index 80dedd2..a6e52ff 100644
--- a/java/res/drawable-xhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
+++ b/java/res/drawable-xhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_active_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_active_lxx_dark.9.png
new file mode 100644
index 0000000..88a0424
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_active_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png
new file mode 100644
index 0000000..1d3a79a
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_active_pressed_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
index 97b049e..8147586 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_normal_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
index 2e81497..598bb68 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_normal_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
index d844b17..963f34a 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_off_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
index 9661f4a..b40d6f4 100644
--- a/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_pressed_on_lxx_dark.9.png
Binary files differ
diff --git a/java/res/drawable/btn_keyboard_key_lxx_dark.xml b/java/res/drawable/btn_keyboard_key_lxx_dark.xml
index c56d4b3..bb1789a 100644
--- a/java/res/drawable/btn_keyboard_key_lxx_dark.xml
+++ b/java/res/drawable/btn_keyboard_key_lxx_dark.xml
@@ -17,9 +17,9 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Action keys. -->
     <item android:state_active="true" android:state_pressed="true"
-          android:drawable="@color/key_background_pressed_lxx_dark" />
+          android:drawable="@drawable/btn_keyboard_key_active_pressed_lxx_dark" />
     <item android:state_active="true"
-          android:drawable="@color/key_background_lxx_dark" />
+          android:drawable="@drawable/btn_keyboard_key_active_lxx_dark" />
 
     <!-- Toggle keys. Use checkable/checked state. -->
     <item android:state_checkable="true" android:state_checked="true" android:state_pressed="true"
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 65242dd..4a2b37e 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -130,7 +130,7 @@
     public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) {
         if (mCurrentKey != null && mActivePointerId == pointerId) {
             updateReleaseKeyGraphics(mCurrentKey);
-            onCodeInput(mCurrentKey.getCode(), x, y);
+            onKeyInput(mCurrentKey, x, y);
             mCurrentKey = null;
         }
     }
@@ -138,7 +138,8 @@
     /**
      * Performs the specific action for this panel when the user presses a key on the panel.
      */
-    protected void onCodeInput(final int code, final int x, final int y) {
+    protected void onKeyInput(final Key key, final int x, final int y) {
+        final int code = key.getCode();
         if (code == Constants.CODE_OUTPUT_TEXT) {
             mListener.onTextInput(mCurrentKey.getOutputText());
         } else if (code != Constants.CODE_UNSPECIFIED) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b8cf3f8..e7ab02a 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -359,14 +359,16 @@
     }
 
     @UsedForTesting
-    public boolean isValidBigram(final String word0, final String word1) {
-        return getBigramProbability(word0, word1) != NOT_A_PROBABILITY;
+    public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
+        return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
     }
 
-    public int getBigramProbability(final String word0, final String word1) {
-        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return NOT_A_PROBABILITY;
-        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
-        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+    public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
+        if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
+            return NOT_A_PROBABILITY;
+        }
+        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+        final int[] codePoints1 = StringUtils.toCodePointArray(word);
         return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
     }
 
@@ -417,7 +419,7 @@
     }
 
     // Add a unigram entry to binary dictionary with unigram attributes in native code.
-    public void addUnigramWord(final String word, final int probability,
+    public void addUnigramEntry(final String word, final int probability,
             final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
         if (TextUtils.isEmpty(word)) {
@@ -431,25 +433,26 @@
         mHasUpdated = true;
     }
 
-    // Add a bigram entry to binary dictionary with timestamp in native code.
-    public void addBigramWords(final String word0, final String word1, final int probability,
+    // Add an n-gram entry to the binary dictionary with timestamp in native code.
+    public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
+            final int probability,
             final int timestamp) {
-        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
+        if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
             return;
         }
-        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
-        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+        final int[] codePoints1 = StringUtils.toCodePointArray(word);
         addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
         mHasUpdated = true;
     }
 
-    // Remove a bigram entry form binary dictionary in native code.
-    public void removeBigramWords(final String word0, final String word1) {
-        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
+    // Remove an n-gram entry from the binary dictionary in native code.
+    public void removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
+        if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) {
             return;
         }
-        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
-        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+        final int[] codePoints1 = StringUtils.toCodePointArray(word);
         removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
         mHasUpdated = true;
     }
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index e04fcda..3fb76b1 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -142,7 +142,7 @@
                 Log.d(TAG, "loadAccountVocabulary: " + word);
             }
             runGCIfRequiredLocked(true /* mindsBlockByGC */);
-            addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
+            addUnigramLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
                     0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */,
                     BinaryDictionary.NOT_A_VALID_TIMESTAMP);
         }
@@ -224,7 +224,7 @@
      */
     private void addNameLocked(final String name) {
         int len = StringUtils.codePointCount(name);
-        String prevWord = null;
+        PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null);
         // TODO: Better tokenization for non-Latin writing systems
         for (int i = 0; i < len; i++) {
             if (Character.isLetter(name.codePointAt(i))) {
@@ -239,19 +239,19 @@
                 final int wordLen = StringUtils.codePointCount(word);
                 if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
                     if (DEBUG) {
-                        Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord);
+                        Log.d(TAG, "addName " + name + ", " + word + ", "
+                                + prevWordsInfo.mPrevWord);
                     }
                     runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                    addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS,
+                    addUnigramLocked(word, FREQUENCY_FOR_CONTACTS,
                             null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */,
                             false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
-                    if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
+                    if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && mUseFirstLastBigrams) {
                         runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                        addBigramDynamicallyLocked(prevWord, word,
-                                FREQUENCY_FOR_CONTACTS_BIGRAM,
+                        addNgramEntryLocked(prevWordsInfo, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
                                 BinaryDictionary.NOT_A_VALID_TIMESTAMP);
                     }
-                    prevWord = word;
+                    prevWordsInfo = new PrevWordsInfo(word);
                 }
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index 14c8bb6..301b832 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -370,22 +370,23 @@
     }
 
     public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
-            final String previousWord, final int timeStampInSeconds,
+            final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
             final boolean blockPotentiallyOffensive) {
         final Dictionaries dictionaries = mDictionaries;
         final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
         for (int i = 0; i < words.length; i++) {
             final String currentWord = words[i];
-            final String prevWord = (i == 0) ? previousWord : words[i - 1];
+            final PrevWordsInfo prevWordsInfoForCurrentWord =
+                    (i == 0) ? prevWordsInfo : new PrevWordsInfo(words[i - 1]);
             final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
-            addWordToUserHistory(dictionaries, prevWord, currentWord,
+            addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord,
                     wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
         }
     }
 
-    private void addWordToUserHistory(final Dictionaries dictionaries, final String prevWord,
-            final String word, final boolean wasAutoCapitalized, final int timeStampInSeconds,
-            final boolean blockPotentiallyOffensive) {
+    private void addWordToUserHistory(final Dictionaries dictionaries,
+            final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized,
+            final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
         final ExpandableBinaryDictionary userHistoryDictionary =
                 dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         if (userHistoryDictionary == null) {
@@ -430,15 +431,16 @@
         // 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 boolean isValid = maxFreq > 0;
-        UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWord, secondWord,
+        UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord,
                 isValid, timeStampInSeconds);
     }
 
-    public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
+    public void cancelAddingUserHistory(final PrevWordsInfo prevWordsInfo,
+            final String committedWord) {
         final ExpandableBinaryDictionary userHistoryDictionary =
                 mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         if (userHistoryDictionary != null) {
-            userHistoryDictionary.removeBigramDynamically(previousWord, committedWord);
+            userHistoryDictionary.removeNgramDynamically(prevWordsInfo, committedWord);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 629f3fd..d67253c 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -269,9 +269,9 @@
     }
 
     /**
-     * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
+     * Adds unigram information of a word to the dictionary. May overwrite an existing entry.
      */
-    public void addWordDynamically(final String word, final int frequency,
+    public void addUnigramEntry(final String word, final int frequency,
             final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
         reloadDictionaryIfRequired();
@@ -282,23 +282,23 @@
                     return;
                 }
                 runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq,
+                addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq,
                         isNotAWord, isBlacklisted, timestamp);
             }
         });
     }
 
-    protected void addWordDynamicallyLocked(final String word, final int frequency,
+    protected void addUnigramLocked(final String word, final int frequency,
             final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
-        mBinaryDictionary.addUnigramWord(word, frequency, shortcutTarget, shortcutFreq,
+        mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq,
                 isNotAWord, isBlacklisted, timestamp);
     }
 
     /**
-     * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
+     * Adds n-gram information of a word to the dictionary. May overwrite an existing entry.
      */
-    public void addBigramDynamically(final String word0, final String word1,
+    public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
             final int frequency, final int timestamp) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
@@ -308,20 +308,20 @@
                     return;
                 }
                 runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                addBigramDynamicallyLocked(word0, word1, frequency, timestamp);
+                addNgramEntryLocked(prevWordsInfo, word, frequency, timestamp);
             }
         });
     }
 
-    protected void addBigramDynamicallyLocked(final String word0, final String word1,
+    protected void addNgramEntryLocked(final PrevWordsInfo prevWordsInfo, final String word,
             final int frequency, final int timestamp) {
-        mBinaryDictionary.addBigramWords(word0, word1, frequency, timestamp);
+        mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp);
     }
 
     /**
-     * Dynamically remove a word bigram in the dictionary.
+     * Dynamically remove the n-gram entry in the dictionary.
      */
-    public void removeBigramDynamically(final String word0, final String word1) {
+    public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word1) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
             @Override
@@ -330,7 +330,7 @@
                     return;
                 }
                 runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                mBinaryDictionary.removeBigramWords(word0, word1);
+                mBinaryDictionary.removeNgramEntry(prevWordsInfo, word1);
             }
         });
     }
@@ -428,9 +428,9 @@
         return mBinaryDictionary.isValidWord(word);
     }
 
-    protected boolean isValidBigramLocked(final String word1, final String word2) {
+    protected boolean isValidNgramLocked(final PrevWordsInfo prevWordsInfo, final String word) {
         if (mBinaryDictionary == null) return false;
-        return mBinaryDictionary.isValidBigram(word1, word2);
+        return mBinaryDictionary.isValidNgram(prevWordsInfo, word);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 232bf74..9caec3e 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -48,7 +48,7 @@
     public final String mTypedWord;
     public final CharSequence mCommittedWord;
     public final String mSeparatorString;
-    public final String mPrevWord;
+    public final PrevWordsInfo mPrevWordsInfo;
     public final int mCapitalizedMode;
     public final InputPointers mInputPointers =
             new InputPointers(Constants.DICTIONARY_MAX_WORD_LENGTH);
@@ -64,7 +64,7 @@
     public LastComposedWord(final ArrayList<Event> events,
             final InputPointers inputPointers, final String typedWord,
             final CharSequence committedWord, final String separatorString,
-            final String prevWord, final int capitalizedMode) {
+            final PrevWordsInfo prevWordsInfo, final int capitalizedMode) {
         if (inputPointers != null) {
             mInputPointers.copy(inputPointers);
         }
@@ -73,7 +73,7 @@
         mCommittedWord = committedWord;
         mSeparatorString = separatorString;
         mActive = true;
-        mPrevWord = prevWord;
+        mPrevWordsInfo = prevWordsInfo;
         mCapitalizedMode = capitalizedMode;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5e45275..19c777a 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1435,12 +1435,13 @@
                 // We're checking the previous word in the text field against the memorized previous
                 // word. If we are composing a word we should have the second word before the cursor
                 // memorized, otherwise we should have the first.
-                final CharSequence rereadPrevWord = mInputLogic.getNthPreviousWordForSuggestion(
-                        currentSettings.mSpacingAndPunctuations,
-                        mInputLogic.mWordComposer.isComposingWord() ? 2 : 1);
-                if (!TextUtils.equals(prevWordsInfo.mPrevWord, rereadPrevWord)) {
+                final PrevWordsInfo rereadPrevWordsInfo =
+                        mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion(
+                                currentSettings.mSpacingAndPunctuations,
+                                mInputLogic.mWordComposer.isComposingWord() ? 2 : 1);
+                if (!TextUtils.equals(prevWordsInfo.mPrevWord, rereadPrevWordsInfo.mPrevWord)) {
                     throw new RuntimeException("Unexpected previous word: "
-                            + prevWordsInfo.mPrevWord + " <> " + rereadPrevWord);
+                            + prevWordsInfo.mPrevWord + " <> " + rereadPrevWordsInfo.mPrevWord);
                 }
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
index 9d85431..ecc8947 100644
--- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
+++ b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
@@ -16,6 +16,9 @@
 
 package com.android.inputmethod.latin;
 
+import android.util.Log;
+
+// TODO: Support multiple previous words for n-gram.
 public class PrevWordsInfo {
     // The previous word. May be null after resetting and before starting a new composing word, or
     // when there is no context like at the start of text for example. It can also be set to null
@@ -23,7 +26,18 @@
     // or a comma.
     public final String mPrevWord;
 
+    // TODO: Have sentence separator.
+    // Whether the current context is beginning of sentence or not.
+    public final boolean mIsBeginningOfSentence;
+
+    // Beginning of sentence.
+    public PrevWordsInfo() {
+        mPrevWord = null;
+        mIsBeginningOfSentence = true;
+    }
+
     public PrevWordsInfo(final String prevWord) {
         mPrevWord = prevWord;
+        mIsBeginningOfSentence = false;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 606bb77..2c54e10 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -538,10 +538,12 @@
     }
 
     @SuppressWarnings("unused")
-    public String getNthPreviousWord(final SpacingAndPunctuations spacingAndPunctuations,
-            final int n) {
+    public PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(
+            final SpacingAndPunctuations spacingAndPunctuations, final int n) {
         mIC = mParent.getCurrentInputConnection();
-        if (null == mIC) return null;
+        if (null == mIC) {
+            return new PrevWordsInfo(null);
+        }
         final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
         if (DEBUG_PREVIOUS_TEXT && null != prev) {
             final int checkLength = LOOKBACK_CHARACTER_NUM - 1;
@@ -561,46 +563,57 @@
                 }
             }
         }
-        return getNthPreviousWord(prev, spacingAndPunctuations, n);
+        return getPrevWordsInfoFromNthPreviousWord(prev, spacingAndPunctuations, n);
     }
 
     private static boolean isSeparator(final int code, final int[] sortedSeparators) {
         return Arrays.binarySearch(sortedSeparators, code) >= 0;
     }
 
-    // Get the nth word before cursor. n = 1 retrieves the word immediately before the cursor,
-    // n = 2 retrieves the word before that, and so on. This splits on whitespace only.
+    // Get information of the nth word before cursor. n = 1 retrieves the word immediately before
+    // the cursor, n = 2 retrieves the word before that, and so on. This splits on whitespace only.
     // Also, it won't return words that end in a separator (if the nth word before the cursor
-    // ends in a separator, it returns null).
+    // ends in a separator, it returns information represents beginning-of-sentence).
     // Example :
     // (n = 1) "abc def|" -> def
     // (n = 1) "abc def |" -> def
-    // (n = 1) "abc def. |" -> null
-    // (n = 1) "abc def . |" -> null
+    // (n = 1) "abc def. |" -> beginning-of-sentence
+    // (n = 1) "abc def . |" -> beginning-of-sentence
     // (n = 2) "abc def|" -> abc
     // (n = 2) "abc def |" -> abc
     // (n = 2) "abc def. |" -> abc
     // (n = 2) "abc def . |" -> def
-    // (n = 2) "abc|" -> null
-    // (n = 2) "abc |" -> null
-    // (n = 2) "abc. def|" -> null
-    public static String getNthPreviousWord(final CharSequence prev,
+    // (n = 2) "abc|" -> beginning-of-sentence
+    // (n = 2) "abc |" -> beginning-of-sentence
+    // (n = 2) "abc. def|" -> beginning-of-sentence
+    public static PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(final CharSequence prev,
             final SpacingAndPunctuations spacingAndPunctuations, final int n) {
-        if (prev == null) return null;
+        if (prev == null) return new PrevWordsInfo(null);
         final String[] w = spaceRegex.split(prev);
 
-        // If we can't find n words, or we found an empty word, return null.
-        if (w.length < n) return null;
+        // If we can't find n words, or we found an empty word, the context is
+        // beginning-of-sentence.
+        if (w.length < n) {
+            return new PrevWordsInfo();
+        }
         final String nthPrevWord = w[w.length - n];
         final int length = nthPrevWord.length();
-        if (length <= 0) return null;
+        if (length <= 0) {
+            return  new PrevWordsInfo();
+        }
 
-        // If ends in a separator, return null
+        // If ends in a sentence separator, the context is beginning-of-sentence.
         final char lastChar = nthPrevWord.charAt(length - 1);
+        if (spacingAndPunctuations.isSentenceSeparator(lastChar)) {
+            new PrevWordsInfo();
+        }
+        // If ends in a word separator or connector, the context is unclear.
+        // TODO: Return meaningful context for this case.
         if (spacingAndPunctuations.isWordSeparator(lastChar)
-                || spacingAndPunctuations.isWordConnector(lastChar)) return null;
-
-        return nthPrevWord;
+                || spacingAndPunctuations.isWordConnector(lastChar)) {
+            return new PrevWordsInfo(null);
+        }
+        return new PrevWordsInfo(nthPrevWord);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index c8ffbe4..b89ab84 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -258,12 +258,12 @@
                 // Safeguard against adding really long words.
                 if (word.length() < MAX_WORD_LENGTH) {
                     runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                    addWordDynamicallyLocked(word, adjustedFrequency, null /* shortcutTarget */,
+                    addUnigramLocked(word, adjustedFrequency, null /* shortcutTarget */,
                             0 /* shortcutFreq */, false /* isNotAWord */,
                             false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
                     if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
                         runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                        addWordDynamicallyLocked(shortcut, adjustedFrequency, word,
+                        addUnigramLocked(shortcut, adjustedFrequency, word,
                                 USER_DICT_SHORTCUT_FREQUENCY, true /* isNotAWord */,
                                 false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
                     }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 227b42b..6ecb373 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -294,11 +294,10 @@
      * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
      * @param codePoints the code points to set as the composing word.
      * @param coordinates the x, y coordinates of the key in the CoordinateUtils format
-     * @param previousWord the previous word, to use as context for suggestions. Can be null if
-     *   the context is nil (typically, at start of text).
+     * @param prevWordsInfo the information of previous words, to use as context for suggestions
      */
     public void setComposingWord(final int[] codePoints, final int[] coordinates,
-            final CharSequence previousWord) {
+            final PrevWordsInfo prevWordsInfo) {
         reset();
         final int length = codePoints.length;
         for (int i = 0; i < length; ++i) {
@@ -307,7 +306,7 @@
                     CoordinateUtils.yFromArray(coordinates, i)));
         }
         mIsResumed = true;
-        mPrevWordsInfo = new PrevWordsInfo(null == previousWord ? null : previousWord.toString());
+        mPrevWordsInfo = prevWordsInfo;
     }
 
     /**
@@ -372,12 +371,12 @@
      * Also, batch input needs to know about the current caps mode to display correctly
      * capitalized suggestions.
      * @param mode the mode at the time of start
-     * @param previousWord the previous word as context for suggestions. May be null if none.
+     * @param prevWordsInfo the information of previous words
      */
     public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode,
-            final CharSequence previousWord) {
+            final PrevWordsInfo prevWordsInfo) {
         mCapitalizedMode = mode;
-        mPrevWordsInfo = new PrevWordsInfo(null == previousWord ? null : previousWord.toString());
+        mPrevWordsInfo = prevWordsInfo;
     }
 
     /**
@@ -413,13 +412,13 @@
     // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
     // committedWord should contain suggestion spans if applicable.
     public LastComposedWord commitWord(final int type, final CharSequence committedWord,
-            final String separatorString, final String prevWord) {
+            final String separatorString, final PrevWordsInfo prevWordsInfo) {
         // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
         // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
         // the last composed word to ensure this does not happen.
         final LastComposedWord lastComposedWord = new LastComposedWord(mEvents,
                 mInputPointers, mTypedWordCache.toString(), committedWord, separatorString,
-                prevWord, mCapitalizedMode);
+                prevWordsInfo, mCapitalizedMode);
         mInputPointers.reset();
         if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
                 && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index ea58abc..faab769 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -37,6 +37,7 @@
 import com.android.inputmethod.latin.LastComposedWord;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.RichInputConnection;
 import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
@@ -574,7 +575,7 @@
         mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
                 getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()),
                 // Prev word is 1st word before cursor
-                getNthPreviousWordForSuggestion(
+                getPrevWordsInfoFromNthPreviousWordForSuggestion(
                         settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
     }
 
@@ -613,7 +614,8 @@
                             getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState());
                     mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
                             getActualCapsMode(settingsValues,
-                                    keyboardSwitcher.getKeyboardShiftMode()), commitParts[0]);
+                                    keyboardSwitcher.getKeyboardShiftMode()),
+                                            new PrevWordsInfo(commitParts[0]));
                     ++mAutoCommitSequenceNumber;
                 }
             }
@@ -764,7 +766,8 @@
                 // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
                 // yet, so the word we want is the 1st word before the cursor.
                 mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
-                        inputTransaction.mShiftState, getNthPreviousWordForSuggestion(
+                        inputTransaction.mShiftState,
+                        getPrevWordsInfoFromNthPreviousWordForSuggestion(
                                 settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
             }
             mConnection.setComposingText(getTextWithUnderline(
@@ -1233,7 +1236,7 @@
     }
 
     private void performAdditionToUserHistoryDictionary(final SettingsValues settingsValues,
-            final String suggestion, final String prevWord) {
+            final String suggestion, final PrevWordsInfo prevWordsInfo) {
         // If correction is not enabled, we don't add words to the user history dictionary.
         // That's to avoid unintended additions in some sensitive fields, or fields that
         // expect to receive non-words.
@@ -1244,8 +1247,8 @@
                 mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps();
         final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
                 System.currentTimeMillis());
-        mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord,
-                timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
+        mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized,
+                prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
     }
 
     public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
@@ -1325,7 +1328,8 @@
             // Show predictions.
             mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
                     WordComposer.CAPS_MODE_OFF,
-                    getNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations, 1));
+                    getPrevWordsInfoFromNthPreviousWordForSuggestion(
+                            settingsValues.mSpacingAndPunctuations, 1));
             mLatinIME.mHandler.postUpdateSuggestionStrip();
             return;
         }
@@ -1370,13 +1374,14 @@
             }
         }
         final int[] codePoints = StringUtils.toCodePointArray(typedWord);
+        // We want the previous word for suggestion. If we have chars in the word
+        // before the cursor, then we want the word before that, hence 2; otherwise,
+        // we want the word immediately before the cursor, hence 1.
+        final PrevWordsInfo prevWordsInfo = getPrevWordsInfoFromNthPreviousWordForSuggestion(
+                settingsValues.mSpacingAndPunctuations,
+                0 == numberOfCharsInWordBeforeCursor ? 1 : 2);
         mWordComposer.setComposingWord(codePoints,
-                mLatinIME.getCoordinatesForCurrentKeyboard(codePoints),
-                getNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations,
-                        // We want the previous word for suggestion. If we have chars in the word
-                        // before the cursor, then we want the word before that, hence 2; otherwise,
-                        // we want the word immediately before the cursor, hence 1.
-                        0 == numberOfCharsInWordBeforeCursor ? 1 : 2));
+                mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo);
         mWordComposer.setCursorPositionWithinWord(
                 typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
         mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
@@ -1431,7 +1436,7 @@
      * @param inputTransaction The transaction in progress.
      */
     private void revertCommit(final InputTransaction inputTransaction) {
-        final String previousWord = mLastComposedWord.mPrevWord;
+        final PrevWordsInfo prevWordsInfo = mLastComposedWord.mPrevWordsInfo;
         final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
         final CharSequence committedWord = mLastComposedWord.mCommittedWord;
         final String committedWordString = committedWord.toString();
@@ -1453,9 +1458,9 @@
             }
         }
         mConnection.deleteSurroundingText(deleteLength, 0);
-        if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
+        if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && !TextUtils.isEmpty(committedWord)) {
             mSuggest.mDictionaryFacilitator.cancelAddingUserHistory(
-                    previousWord, committedWordString);
+                    prevWordsInfo, committedWordString);
         }
         final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
         final SpannableString textToCommit = new SpannableString(stringToCommit);
@@ -1504,7 +1509,7 @@
             // with the typed word, so we need to resume suggestions right away.
             final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
             mWordComposer.setComposingWord(codePoints,
-                    mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), previousWord);
+                    mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo);
             mConnection.setComposingText(textToCommit, 1);
         }
         if (inputTransaction.mSettingsValues.mIsInternal) {
@@ -1586,21 +1591,23 @@
     }
 
     /**
-     * Get the nth previous word before the cursor as context for the suggestion process.
+     * Get information fo previous words from the nth previous word before the cursor as context
+     * for the suggestion process.
      * @param spacingAndPunctuations the current spacing and punctuations settings.
      * @param nthPreviousWord reverse index of the word to get (1-indexed)
-     * @return the nth previous word before the cursor.
+     * @return the information of previous words
      */
     // TODO: Make this private
-    public CharSequence getNthPreviousWordForSuggestion(
+    public PrevWordsInfo getPrevWordsInfoFromNthPreviousWordForSuggestion(
             final SpacingAndPunctuations spacingAndPunctuations, final int nthPreviousWord) {
         if (spacingAndPunctuations.mCurrentLanguageHasSpaces) {
             // If we are typing in a language with spaces we can just look up the previous
-            // word from textview.
-            return mConnection.getNthPreviousWord(spacingAndPunctuations, nthPreviousWord);
+            // word information from textview.
+            return mConnection.getPrevWordsInfoFromNthPreviousWord(
+                    spacingAndPunctuations, nthPreviousWord);
         } else {
-            return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
-                    : mLastComposedWord.mCommittedWord;
+            return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? new PrevWordsInfo()
+                    : new PrevWordsInfo(mLastComposedWord.mCommittedWord.toString());
         }
     }
 
@@ -1968,17 +1975,17 @@
                         suggestedWords);
         // Use the 2nd previous word as the previous word because the 1st previous word is the word
         // to be committed.
-        final String prevWord = mConnection.getNthPreviousWord(
+        final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord(
                 settingsValues.mSpacingAndPunctuations, 2);
         mConnection.commitText(chosenWordWithSuggestions, 1);
         // Add the word to the user history dictionary
-        performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWord);
+        performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
         // TODO: figure out here if this is an auto-correct or if the best word is actually
         // what user typed. Note: currently this is done much later in
         // LastComposedWord#didCommitTypedWord by string equality of the remembered
         // strings.
         mLastComposedWord = mWordComposer.commitWord(commitType,
-                chosenWordWithSuggestions, separatorString, prevWord);
+                chosenWordWithSuggestions, separatorString, prevWordsInfo);
         final boolean shouldDiscardPreviousWordForSuggestion;
         if (0 == StringUtils.codePointCount(separatorString)) {
             // Separator is 0-length, we can keep the previous word for suggestion. Either this
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 818cd9a..f89caf9 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
 
 import java.io.File;
 import java.util.Locale;
@@ -52,29 +53,32 @@
     }
 
     /**
-     * Pair will be added to the user history dictionary.
+     * Add a word to the user history dictionary.
      *
-     * The first word may be null. That means we don't know the context, in other words,
-     * it's only a unigram. The first word may also be an empty string : this means start
-     * context, as in beginning of a sentence for example.
-     * The second word may not be null (a NullPointerException would be thrown).
+     * @param userHistoryDictionary the user history dictionary
+     * @param prevWordsInfo the information of previous words
+     * @param word the word the user inputted
+     * @param isValid whether the word is valid or not
+     * @param timestamp the timestamp when the word has been inputted
      */
     public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
-            final String word0, final String word1, final boolean isValid, final int timestamp) {
-        if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
-                (word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
+            final PrevWordsInfo prevWordsInfo, final String word, final boolean isValid,
+            final int timestamp) {
+        final String prevWord = prevWordsInfo.mPrevWord;
+        if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
+                (prevWord != null && prevWord.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
             return;
         }
         final int frequency = isValid ?
                 FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
-        userHistoryDictionary.addWordDynamically(word1, frequency, null /* shortcutTarget */,
+        userHistoryDictionary.addUnigramEntry(word, frequency, null /* shortcutTarget */,
                 0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */, timestamp);
         // Do not insert a word as a bigram of itself
-        if (word1.equals(word0)) {
+        if (word.equals(prevWord)) {
             return;
         }
-        if (null != word0) {
-            userHistoryDictionary.addBigramDynamically(word0, word1, frequency, timestamp);
+        if (null != prevWord) {
+            userHistoryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 5a325ea..e90b15c 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -27,14 +27,13 @@
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreSuggestions extends Keyboard {
-    public static final int SUGGESTION_CODE_BASE = 1024;
-
     public final SuggestedWords mSuggestedWords;
 
     public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter {
@@ -178,7 +177,7 @@
         }
     }
 
-    private static boolean isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords,
+    static boolean isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords,
             final int index) {
         return suggestedWords.mWillAutoCorrect && index == SuggestedWords.INDEX_OF_AUTO_CORRECTION;
     }
@@ -226,11 +225,7 @@
                     word = mSuggestedWords.getLabel(index);
                     info = mSuggestedWords.getDebugString(index);
                 }
-                final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
-                final Key key = new Key(word, KeyboardIconsSet.ICON_UNDEFINED,
-                        indexInMoreSuggestions, null /* outputText */, info, 0 /* labelFlags */,
-                        Key.BACKGROUND_TYPE_NORMAL, x, y, width, params.mDefaultRowHeight,
-                        params.mHorizontalGap, params.mVerticalGap);
+                final Key key = new MoreSuggestionKey(word, info, index, params);
                 params.markAsEdgeKey(key, index);
                 params.onAddKey(key);
                 final int columnNumber = params.getColumnNumber(index);
@@ -245,6 +240,19 @@
         }
     }
 
+    static final class MoreSuggestionKey extends Key {
+        public final int mSuggestedWordIndex;
+
+        public MoreSuggestionKey(final String word, final String info, final int index,
+                final MoreSuggestionsParam params) {
+            super(word /* label */, KeyboardIconsSet.ICON_UNDEFINED, Constants.CODE_OUTPUT_TEXT,
+                    word /* outputText */, info, 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL,
+                    params.getX(index), params.getY(index), params.getWidth(index),
+                    params.mDefaultRowHeight, params.mHorizontalGap, params.mVerticalGap);
+            mSuggestedWordIndex = index;
+        }
+    }
+
     private static final class Divider extends Key.Spacer {
         private final Drawable mIcon;
 
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 549ff0d..7fd64c4 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -20,10 +20,12 @@
 import android.util.AttributeSet;
 import android.util.Log;
 
+import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.MoreKeysKeyboardView;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionKey;
 import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
 
 /**
@@ -59,7 +61,12 @@
     }
 
     @Override
-    public void onCodeInput(final int code, final int x, final int y) {
+    protected void onKeyInput(final Key key, final int x, final int y) {
+        if (!(key instanceof MoreSuggestionKey)) {
+            Log.e(TAG, "Expected key is MoreSuggestionKey, but found "
+                    + key.getClass().getName());
+            return;
+        }
         final Keyboard keyboard = getKeyboard();
         if (!(keyboard instanceof MoreSuggestions)) {
             Log.e(TAG, "Expected keyboard is MoreSuggestions, but found "
@@ -67,7 +74,7 @@
             return;
         }
         final SuggestedWords suggestedWords = ((MoreSuggestions)keyboard).mSuggestedWords;
-        final int index = code - MoreSuggestions.SUGGESTION_CODE_BASE;
+        final int index = ((MoreSuggestionKey)key).mSuggestedWordIndex;
         if (index < 0 || index >= suggestedWords.size()) {
             Log.e(TAG, "Selected suggestion has an illegal index: " + index);
             return;
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index a219532..9ea7e21 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -114,7 +114,7 @@
         final int[] codePoints = StringUtils.toCodePointArray(testedWord);
         final int[] coordinates;
         coordinates = mKeyboard.getCoordinates(codePoints);
-        composer.setComposingWord(codePoints, coordinates, prevWordsInfo.mPrevWord);
+        composer.setComposingWord(codePoints, coordinates, prevWordsInfo);
 
         final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
         final String consideredWord = trailingSingleQuotesCount > 0 ?
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
index 13955b8..26402f8 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutSetSubtypesCountTests.java
@@ -25,7 +25,7 @@
 
 @SmallTest
 public class KeyboardLayoutSetSubtypesCountTests extends KeyboardLayoutSetTestsBase {
-    private static final int NUMBER_OF_SUBTYPES = 71;
+    private static final int NUMBER_OF_SUBTYPES = 68;
     private static final int NUMBER_OF_ASCII_CAPABLE_SUBTYPES = 45;
     private static final int NUMBER_OF_PREDEFINED_ADDITIONAL_SUBTYPES = 2;
 
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindiCompact.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindiCompact.java
index 2e676df..6380da5 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindiCompact.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsHindiCompact.java
@@ -16,7 +16,7 @@
 
 package com.android.inputmethod.keyboard.layout.tests;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
 
 import com.android.inputmethod.keyboard.layout.HindiCompact;
 import com.android.inputmethod.keyboard.layout.HindiCompact.HindiCompactCustomizer;
@@ -27,7 +27,7 @@
 /**
  * hi: Hindi/hindi_compact
  */
-@SmallTest
+@Suppress
 public final class TestsHindiCompact extends LayoutTestsBase {
     private static final Locale LOCALE = new Locale("hi");
     private static final LayoutBase LAYOUT = new HindiCompact(new HindiCompactCustomizer(LOCALE));
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMarathiIN.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMarathiIN.java
index b937629..d45d99d 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMarathiIN.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMarathiIN.java
@@ -16,7 +16,7 @@
 
 package com.android.inputmethod.keyboard.layout.tests;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
 
 import com.android.inputmethod.keyboard.layout.LayoutBase;
 import com.android.inputmethod.keyboard.layout.Marathi;
@@ -27,7 +27,7 @@
 /**
  * mr_IN: Marathi (India)/marathi
  */
-@SmallTest
+@Suppress
 public final class TestsMarathiIN extends LayoutTestsBase {
     private static final Locale LOCALE = new Locale("mr", "IN");
     private static final LayoutBase LAYOUT = new Marathi(new MarathiCustomizer(LOCALE));
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMyanmarMM.java b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMyanmarMM.java
index e6d3b3b..a0bd50c 100644
--- a/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMyanmarMM.java
+++ b/tests/src/com/android/inputmethod/keyboard/layout/tests/TestsMyanmarMM.java
@@ -16,7 +16,7 @@
 
 package com.android.inputmethod.keyboard.layout.tests;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
 
 import com.android.inputmethod.keyboard.layout.LayoutBase;
 import com.android.inputmethod.keyboard.layout.Myanmar;
@@ -27,7 +27,7 @@
 /**
  * my_MM: Myanmar (Myanmar)/myanmar
  */
-@SmallTest
+@Suppress
 public final class TestsMyanmarMM extends LayoutTestsBase {
     private static final Locale LOCALE = new Locale("my", "MM");
     private static final LayoutBase LAYOUT = new Myanmar(new MyanmarCustomizer(LOCALE));
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index 90b90ff..2c2fed3 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -65,7 +65,7 @@
 
     private void addUnigramWord(final BinaryDictionary binaryDictionary, final String word,
             final int probability) {
-        binaryDictionary.addUnigramWord(word, probability, "" /* shortcutTarget */,
+        binaryDictionary.addUnigramEntry(word, probability, "" /* shortcutTarget */,
                 BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */,
                 false /* isNotAWord */, false /* isBlacklisted */,
                 mCurrentTime /* timestamp */);
@@ -73,10 +73,15 @@
 
     private void addBigramWords(final BinaryDictionary binaryDictionary, final String word0,
             final String word1, final int probability) {
-        binaryDictionary.addBigramWords(word0, word1, probability,
+        binaryDictionary.addNgramEntry(new PrevWordsInfo(word0), word1, probability,
                 mCurrentTime /* timestamp */);
     }
 
+    private static boolean isValidBigram(final BinaryDictionary binaryDictionary,
+            final String word0, final String word1) {
+        return binaryDictionary.isValidNgram(new PrevWordsInfo(word0), word1);
+    }
+
     private void forcePassingShortTime(final BinaryDictionary binaryDictionary) {
         // 30 days.
         final int timeToElapse = (int)TimeUnit.SECONDS.convert(30, TimeUnit.DAYS);
@@ -224,19 +229,19 @@
         assertTrue(binaryDictionary.isValidWord("b"));
 
         addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY);
-        assertFalse(binaryDictionary.isValidBigram("a", "b"));
+        assertFalse(isValidBigram(binaryDictionary, "a", "b"));
         addBigramWords(binaryDictionary, "a", "b", Dictionary.NOT_A_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("a", "b"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "b"));
 
         addUnigramWord(binaryDictionary, "c", DUMMY_PROBABILITY);
         addBigramWords(binaryDictionary, "a", "c", DUMMY_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("a", "c"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "c"));
 
         // Add bigrams of not valid unigrams.
         addBigramWords(binaryDictionary, "x", "y", Dictionary.NOT_A_PROBABILITY);
-        assertFalse(binaryDictionary.isValidBigram("x", "y"));
+        assertFalse(isValidBigram(binaryDictionary, "x", "y"));
         addBigramWords(binaryDictionary, "x", "y", DUMMY_PROBABILITY);
-        assertFalse(binaryDictionary.isValidBigram("x", "y"));
+        assertFalse(isValidBigram(binaryDictionary, "x", "y"));
 
         binaryDictionary.close();
         dictFile.delete();
@@ -276,9 +281,9 @@
         addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
         addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("a", "b"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "b"));
         forcePassingShortTime(binaryDictionary);
-        assertFalse(binaryDictionary.isValidBigram("a", "b"));
+        assertFalse(isValidBigram(binaryDictionary, "a", "b"));
 
         addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
@@ -289,11 +294,11 @@
         addUnigramWord(binaryDictionary, "a", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "b", DUMMY_PROBABILITY);
         addBigramWords(binaryDictionary, "a", "b", DUMMY_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("a", "b"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "b"));
         forcePassingShortTime(binaryDictionary);
-        assertTrue(binaryDictionary.isValidBigram("a", "b"));
+        assertTrue(isValidBigram(binaryDictionary, "a", "b"));
         forcePassingLongTime(binaryDictionary);
-        assertFalse(binaryDictionary.isValidBigram("a", "b"));
+        assertFalse(isValidBigram(binaryDictionary, "a", "b"));
 
         binaryDictionary.close();
         dictFile.delete();
@@ -549,8 +554,8 @@
         for (int j = 0; j < weakBigramTypedCount; j++) {
             addBigramWords(binaryDictionary, weak, target, DUMMY_PROBABILITY);
         }
-        assertTrue(binaryDictionary.isValidBigram(strong, target));
-        assertTrue(binaryDictionary.isValidBigram(weak, target));
+        assertTrue(isValidBigram(binaryDictionary, strong, target));
+        assertTrue(isValidBigram(binaryDictionary, weak, target));
 
         for (int i = 0; i < bigramCount; i++) {
             final int word0Index = random.nextInt(words.size());
@@ -571,8 +576,8 @@
                         Integer.parseInt(binaryDictionary.getPropertyForTest(
                                 BinaryDictionary.BIGRAM_COUNT_QUERY));
                 assertTrue(bigramCountBeforeGC > bigramCountAfterGC);
-                assertTrue(binaryDictionary.isValidBigram(strong, target));
-                assertFalse(binaryDictionary.isValidBigram(weak, target));
+                assertTrue(isValidBigram(binaryDictionary, strong, target));
+                assertFalse(isValidBigram(binaryDictionary, weak, target));
                 break;
             }
         }
@@ -606,9 +611,9 @@
         addUnigramWord(binaryDictionary, "ccc", DUMMY_PROBABILITY);
         addUnigramWord(binaryDictionary, "abc", DUMMY_PROBABILITY);
         addBigramWords(binaryDictionary, "aaa", "abc", DUMMY_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abc"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abc"));
         addBigramWords(binaryDictionary, "aaa", "bbb", Dictionary.NOT_A_PROBABILITY);
-        assertFalse(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "bbb"));
 
         assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion());
         assertTrue(binaryDictionary.migrateTo(toFormatVersion));
@@ -619,10 +624,10 @@
         assertTrue(binaryDictionary.getFrequency("aaa") < binaryDictionary.getFrequency("ccc"));
         addUnigramWord(binaryDictionary, "bbb", Dictionary.NOT_A_PROBABILITY);
         assertTrue(binaryDictionary.isValidWord("bbb"));
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abc"));
-        assertFalse(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abc"));
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "bbb"));
         addBigramWords(binaryDictionary, "aaa", "bbb", Dictionary.NOT_A_PROBABILITY);
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb"));
         binaryDictionary.close();
         dictFile.delete();
     }
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
index c87c2a9..2b82e54 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryTests.java
@@ -170,7 +170,7 @@
         addUnigramWord(binaryDictionary, validLongWord, probability);
         addUnigramWord(binaryDictionary, invalidLongWord, probability);
         // Too long short cut.
-        binaryDictionary.addUnigramWord("a", probability, invalidLongWord,
+        binaryDictionary.addUnigramEntry("a", probability, invalidLongWord,
                 10 /* shortcutProbability */, false /* isNotAWord */, false /* isBlacklisted */,
                 BinaryDictionary.NOT_A_VALID_TIMESTAMP);
         addUnigramWord(binaryDictionary, "abc", probability);
@@ -188,20 +188,35 @@
         dictFile.delete();
     }
 
-    private void addUnigramWord(final BinaryDictionary binaryDictionary, final String word,
+    private static void addUnigramWord(final BinaryDictionary binaryDictionary, final String word,
             final int probability) {
-        binaryDictionary.addUnigramWord(word, probability, "" /* shortcutTarget */,
+        binaryDictionary.addUnigramEntry(word, probability, "" /* shortcutTarget */,
                 BinaryDictionary.NOT_A_PROBABILITY /* shortcutProbability */,
                 false /* isNotAWord */, false /* isBlacklisted */,
                 BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */);
     }
 
-    private void addBigramWords(final BinaryDictionary binaryDictionary, final String word0,
+    private static void addBigramWords(final BinaryDictionary binaryDictionary, final String word0,
             final String word1, final int probability) {
-        binaryDictionary.addBigramWords(word0, word1, probability,
+        binaryDictionary.addNgramEntry(new PrevWordsInfo(word0), word1, probability,
                 BinaryDictionary.NOT_A_VALID_TIMESTAMP /* timestamp */);
     }
 
+    private static boolean isValidBigram(final BinaryDictionary binaryDictionary,
+            final String word0, final String word1) {
+        return binaryDictionary.isValidNgram(new PrevWordsInfo(word0), word1);
+    }
+
+    private static void removeBigramEntry(final BinaryDictionary binaryDictionary,
+            final String word0, final String word1) {
+        binaryDictionary.removeNgramEntry(new PrevWordsInfo(word0), word1);
+    }
+
+    private static int getBigramProbability(final BinaryDictionary binaryDictionary,
+            final String word0,  final String word1) {
+        return binaryDictionary.getNgramProbability(new PrevWordsInfo(word0), word1);
+    }
+
     public void testAddUnigramWord() {
         for (final int formatVersion : DICT_FORMAT_VERSIONS) {
             testAddUnigramWord(formatVersion);
@@ -312,32 +327,32 @@
         addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability);
         addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability);
 
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abb"));
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bcc"));
-        assertTrue(binaryDictionary.isValidBigram("abb", "aaa"));
-        assertTrue(binaryDictionary.isValidBigram("abb", "bcc"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bcc"));
+        assertTrue(isValidBigram(binaryDictionary, "abb", "aaa"));
+        assertTrue(isValidBigram(binaryDictionary, "abb", "bcc"));
         if (canCheckBigramProbability(formatVersion)) {
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "abb"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "bcc"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("abb", "aaa"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("abb", "bcc"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc"));
         }
 
         addBigramWords(binaryDictionary, "aaa", "abb", updatedBigramProbability);
         if (canCheckBigramProbability(formatVersion)) {
             assertEquals(updatedBigramProbability,
-                    binaryDictionary.getBigramProbability("aaa", "abb"));
+                    getBigramProbability(binaryDictionary, "aaa", "abb"));
         }
 
-        assertFalse(binaryDictionary.isValidBigram("bcc", "aaa"));
-        assertFalse(binaryDictionary.isValidBigram("bcc", "bbc"));
-        assertFalse(binaryDictionary.isValidBigram("aaa", "aaa"));
+        assertFalse(isValidBigram(binaryDictionary, "bcc", "aaa"));
+        assertFalse(isValidBigram(binaryDictionary, "bcc", "bbc"));
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "aaa"));
         assertEquals(Dictionary.NOT_A_PROBABILITY,
-                binaryDictionary.getBigramProbability("bcc", "aaa"));
+                getBigramProbability(binaryDictionary, "bcc", "aaa"));
         assertEquals(Dictionary.NOT_A_PROBABILITY,
-                binaryDictionary.getBigramProbability("bcc", "bbc"));
+                getBigramProbability(binaryDictionary, "bcc", "bbc"));
         assertEquals(Dictionary.NOT_A_PROBABILITY,
-                binaryDictionary.getBigramProbability("aaa", "aaa"));
+                getBigramProbability(binaryDictionary, "aaa", "aaa"));
 
         // Testing bigram link.
         addUnigramWord(binaryDictionary, "abcde", unigramProbability);
@@ -349,14 +364,14 @@
 
         if (canCheckBigramProbability(formatVersion)) {
             assertEquals(bigramProbability,
-                    binaryDictionary.getBigramProbability("abcde", "fghij"));
+                    getBigramProbability(binaryDictionary, "abcde", "fghij"));
         }
         assertEquals(Dictionary.NOT_A_PROBABILITY,
-                binaryDictionary.getBigramProbability("abcde", "fgh"));
+                getBigramProbability(binaryDictionary, "abcde", "fgh"));
         addBigramWords(binaryDictionary, "abcde", "fghij", updatedBigramProbability);
         if (canCheckBigramProbability(formatVersion)) {
             assertEquals(updatedBigramProbability,
-                    binaryDictionary.getBigramProbability("abcde", "fghij"));
+                    getBigramProbability(binaryDictionary, "abcde", "fghij"));
         }
 
         dictFile.delete();
@@ -418,10 +433,10 @@
         for (final Pair<String, String> bigram : bigramWords) {
             final int bigramProbability = bigramProbabilities.get(bigram);
             assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY,
-                    binaryDictionary.isValidBigram(bigram.first, bigram.second));
+                    isValidBigram(binaryDictionary, bigram.first, bigram.second));
             if (canCheckBigramProbability(formatVersion)) {
                 assertEquals(bigramProbability,
-                        binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+                        getBigramProbability(binaryDictionary, bigram.first, bigram.second));
             }
         }
 
@@ -454,28 +469,28 @@
         addBigramWords(binaryDictionary, "abb", "aaa", bigramProbability);
         addBigramWords(binaryDictionary, "abb", "bcc", bigramProbability);
 
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abb"));
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bcc"));
-        assertTrue(binaryDictionary.isValidBigram("abb", "aaa"));
-        assertTrue(binaryDictionary.isValidBigram("abb", "bcc"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bcc"));
+        assertTrue(isValidBigram(binaryDictionary, "abb", "aaa"));
+        assertTrue(isValidBigram(binaryDictionary, "abb", "bcc"));
 
-        binaryDictionary.removeBigramWords("aaa", "abb");
-        assertFalse(binaryDictionary.isValidBigram("aaa", "abb"));
+        removeBigramEntry(binaryDictionary, "aaa", "abb");
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "abb"));
         addBigramWords(binaryDictionary, "aaa", "abb", bigramProbability);
-        assertTrue(binaryDictionary.isValidBigram("aaa", "abb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "abb"));
 
 
-        binaryDictionary.removeBigramWords("aaa", "bcc");
-        assertFalse(binaryDictionary.isValidBigram("aaa", "bcc"));
-        binaryDictionary.removeBigramWords("abb", "aaa");
-        assertFalse(binaryDictionary.isValidBigram("abb", "aaa"));
-        binaryDictionary.removeBigramWords("abb", "bcc");
-        assertFalse(binaryDictionary.isValidBigram("abb", "bcc"));
+        removeBigramEntry(binaryDictionary, "aaa", "bcc");
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "bcc"));
+        removeBigramEntry(binaryDictionary, "abb", "aaa");
+        assertFalse(isValidBigram(binaryDictionary, "abb", "aaa"));
+        removeBigramEntry(binaryDictionary, "abb", "bcc");
+        assertFalse(isValidBigram(binaryDictionary, "abb", "bcc"));
 
-        binaryDictionary.removeBigramWords("aaa", "abb");
+        removeBigramEntry(binaryDictionary, "aaa", "abb");
         // Test remove non-existing bigram operation.
-        binaryDictionary.removeBigramWords("aaa", "abb");
-        binaryDictionary.removeBigramWords("bcc", "aaa");
+        removeBigramEntry(binaryDictionary, "aaa", "abb");
+        removeBigramEntry(binaryDictionary, "bcc", "aaa");
 
         dictFile.delete();
     }
@@ -570,14 +585,14 @@
         assertEquals(unigramProbability, binaryDictionary.getFrequency("abb"));
         assertEquals(unigramProbability, binaryDictionary.getFrequency("bcc"));
         if (canCheckBigramProbability(formatVersion)) {
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "abb"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "bcc"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("abb", "aaa"));
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("abb", "bcc"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "abb"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bcc"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "aaa"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "abb", "bcc"));
         }
-        assertFalse(binaryDictionary.isValidBigram("bcc", "aaa"));
-        assertFalse(binaryDictionary.isValidBigram("bcc", "bbc"));
-        assertFalse(binaryDictionary.isValidBigram("aaa", "aaa"));
+        assertFalse(isValidBigram(binaryDictionary, "bcc", "aaa"));
+        assertFalse(isValidBigram(binaryDictionary, "bcc", "bbc"));
+        assertFalse(isValidBigram(binaryDictionary, "aaa", "aaa"));
         binaryDictionary.flushWithGC();
         binaryDictionary.close();
 
@@ -649,10 +664,10 @@
         for (final Pair<String, String> bigram : bigramWords) {
             final int bigramProbability = bigramProbabilities.get(bigram);
             assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY,
-                    binaryDictionary.isValidBigram(bigram.first, bigram.second));
+                    isValidBigram(binaryDictionary, bigram.first, bigram.second));
             if (canCheckBigramProbability(formatVersion)) {
                 assertEquals(bigramProbability,
-                        binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+                        getBigramProbability(binaryDictionary, bigram.first, bigram.second));
             }
         }
 
@@ -742,7 +757,7 @@
                     final Pair<String, String> bigram = bigramWords.get(bigramIndex);
                     bigramWords.remove(bigramIndex);
                     bigramProbabilities.remove(bigram);
-                    binaryDictionary.removeBigramWords(bigram.first, bigram.second);
+                    removeBigramEntry(binaryDictionary, bigram.first, bigram.second);
                 }
             }
 
@@ -765,10 +780,10 @@
 
                 if (canCheckBigramProbability(formatVersion)) {
                     assertEquals(probability,
-                            binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+                            getBigramProbability(binaryDictionary, bigram.first, bigram.second));
                 }
                 assertEquals(probability != Dictionary.NOT_A_PROBABILITY,
-                        binaryDictionary.isValidBigram(bigram.first, bigram.second));
+                        isValidBigram(binaryDictionary, bigram.first, bigram.second));
             }
             binaryDictionary.flushWithGC();
             binaryDictionary.close();
@@ -946,10 +961,10 @@
             final String word1 = entry.getKey().second;
             final int bigramProbability = entry.getValue();
             assertEquals(bigramProbability != Dictionary.NOT_A_PROBABILITY,
-                    binaryDictionary.isValidBigram(word0, word1));
+                    isValidBigram(binaryDictionary, word0, word1));
             if (canCheckBigramProbability(formatVersion)) {
                 assertEquals(bigramProbability,
-                        binaryDictionary.getBigramProbability(word0, word1));
+                        getBigramProbability(binaryDictionary, word0, word1));
             }
         }
     }
@@ -993,7 +1008,7 @@
             final boolean isNotAWord = random.nextBoolean();
             final boolean isBlacklisted = random.nextBoolean();
             // TODO: Add tests for historical info.
-            binaryDictionary.addUnigramWord(word, unigramProbability,
+            binaryDictionary.addUnigramEntry(word, unigramProbability,
                     null /* shortcutTarget */, BinaryDictionary.NOT_A_PROBABILITY,
                     isNotAWord, isBlacklisted, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
             if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
@@ -1023,8 +1038,7 @@
             final int unigramProbability = wordProbabilities.get(word1);
             final int bigramProbability =
                     unigramProbability + random.nextInt(0xFF - unigramProbability);
-            binaryDictionary.addBigramWords(word0, word1, bigramProbability,
-                    BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            addBigramWords(binaryDictionary, word0, word1, bigramProbability);
             if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
                 binaryDictionary.flushWithGC();
             }
@@ -1112,8 +1126,7 @@
             final int unigramProbability = wordProbabilitiesToCheckLater.get(word1);
             final int bigramProbability =
                     unigramProbability + random.nextInt(0xFF - unigramProbability);
-            binaryDictionary.addBigramWords(word0, word1, bigramProbability,
-                    BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            addBigramWords(binaryDictionary, word0, word1, bigramProbability);
             if (binaryDictionary.needsToRunGC(false /* mindsBlockByGC */)) {
                 binaryDictionary.flushWithGC();
             }
@@ -1174,7 +1187,7 @@
 
         final int unigramProbability = 100;
         final int shortcutProbability = 10;
-        binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz",
+        binaryDictionary.addUnigramEntry("aaa", unigramProbability, "zzz",
                 shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                 0 /* timestamp */);
         WordProperty wordProperty = binaryDictionary.getWordProperty("aaa");
@@ -1182,7 +1195,7 @@
         assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord);
         assertEquals(shortcutProbability, wordProperty.mShortcutTargets.get(0).getProbability());
         final int updatedShortcutProbability = 2;
-        binaryDictionary.addUnigramWord("aaa", unigramProbability, "zzz",
+        binaryDictionary.addUnigramEntry("aaa", unigramProbability, "zzz",
                 updatedShortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                 0 /* timestamp */);
         wordProperty = binaryDictionary.getWordProperty("aaa");
@@ -1190,7 +1203,7 @@
         assertEquals("zzz", wordProperty.mShortcutTargets.get(0).mWord);
         assertEquals(updatedShortcutProbability,
                 wordProperty.mShortcutTargets.get(0).getProbability());
-        binaryDictionary.addUnigramWord("aaa", unigramProbability, "yyy",
+        binaryDictionary.addUnigramEntry("aaa", unigramProbability, "yyy",
                 shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                 0 /* timestamp */);
         final HashMap<String, Integer> shortcutTargets = new HashMap<String, Integer>();
@@ -1261,7 +1274,7 @@
             final int shortcutProbability = random.nextInt(0xF);
             final String word = words.get(random.nextInt(words.size()));
             final int unigramProbability = unigramProbabilities.get(word);
-            binaryDictionary.addUnigramWord(word, unigramProbability, shortcutTarget,
+            binaryDictionary.addUnigramEntry(word, unigramProbability, shortcutTarget,
                     shortcutProbability, false /* isNotAWord */, false /* isBlacklisted */,
                     0 /* timestamp */);
             if (shortcutTargets.containsKey(word)) {
@@ -1317,14 +1330,14 @@
         final int bigramProbability = 150;
         addBigramWords(binaryDictionary, "aaa", "bbb", bigramProbability);
         final int shortcutProbability = 10;
-        binaryDictionary.addUnigramWord("ccc", unigramProbability, "xxx", shortcutProbability,
+        binaryDictionary.addUnigramEntry("ccc", unigramProbability, "xxx", shortcutProbability,
                 false /* isNotAWord */, false /* isBlacklisted */, 0 /* timestamp */);
-        binaryDictionary.addUnigramWord("ddd", unigramProbability, null /* shortcutTarget */,
+        binaryDictionary.addUnigramEntry("ddd", unigramProbability, null /* shortcutTarget */,
                 Dictionary.NOT_A_PROBABILITY, true /* isNotAWord */,
                 true /* isBlacklisted */, 0 /* timestamp */);
         assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa"));
         assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb"));
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb"));
         assertEquals(fromFormatVersion, binaryDictionary.getFormatVersion());
         assertTrue(binaryDictionary.migrateTo(toFormatVersion));
         assertTrue(binaryDictionary.isValidDictionary());
@@ -1332,9 +1345,9 @@
         assertEquals(unigramProbability, binaryDictionary.getFrequency("aaa"));
         assertEquals(unigramProbability, binaryDictionary.getFrequency("bbb"));
         if (canCheckBigramProbability(toFormatVersion)) {
-            assertEquals(bigramProbability, binaryDictionary.getBigramProbability("aaa", "bbb"));
+            assertEquals(bigramProbability, getBigramProbability(binaryDictionary, "aaa", "bbb"));
         }
-        assertTrue(binaryDictionary.isValidBigram("aaa", "bbb"));
+        assertTrue(isValidBigram(binaryDictionary, "aaa", "bbb"));
         WordProperty wordProperty = binaryDictionary.getWordProperty("ccc");
         assertEquals(1, wordProperty.mShortcutTargets.size());
         assertEquals("xxx", wordProperty.mShortcutTargets.get(0).mWord);
@@ -1395,8 +1408,7 @@
             final int unigramProbability = unigramProbabilities.get(word1);
             final int bigramProbability =
                     random.nextInt(0xFF - unigramProbability) + unigramProbability;
-            binaryDictionary.addBigramWords(word0, word1, bigramProbability,
-                    BinaryDictionary.NOT_A_VALID_TIMESTAMP);
+            addBigramWords(binaryDictionary, word0, word1, bigramProbability);
             if (binaryDictionary.needsToRunGC(true /* mindsBlockByGC */)) {
                 binaryDictionary.flushWithGC();
             }
@@ -1415,9 +1427,9 @@
         for (final Pair<String, String> bigram : bigrams) {
             if (canCheckBigramProbability(toFormatVersion)) {
                 assertEquals((int)bigramProbabilities.get(bigram),
-                        binaryDictionary.getBigramProbability(bigram.first, bigram.second));
+                        getBigramProbability(binaryDictionary, bigram.first, bigram.second));
             }
-            assertTrue(binaryDictionary.isValidBigram(bigram.first, bigram.second));
+            assertTrue(isValidBigram(binaryDictionary, bigram.first, bigram.second));
         }
         assertEquals(bigramProbabilities.size(), Integer.parseInt(
                 binaryDictionary.getPropertyForTest(BinaryDictionary.BIGRAM_COUNT_QUERY)));
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
index 842f3f3..f3351ff 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -155,13 +155,17 @@
      */
     public void testGetPreviousWord() {
         // If one of the following cases breaks, the bigram suggestions won't work.
-        assertEquals(RichInputConnection.getNthPreviousWord(
-                "abc def", mSpacingAndPunctuations, 2), "abc");
-        assertNull(RichInputConnection.getNthPreviousWord(
-                "abc", mSpacingAndPunctuations, 2));
-        assertNull(RichInputConnection.getNthPreviousWord(
-                "abc. def", mSpacingAndPunctuations, 2));
+        assertEquals(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def", mSpacingAndPunctuations, 2).mPrevWord, "abc");
+        assertNull(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc", mSpacingAndPunctuations, 2).mPrevWord);
+        assertNull(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc. def", mSpacingAndPunctuations, 2).mPrevWord);
 
+        assertFalse(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def", mSpacingAndPunctuations, 2).mIsBeginningOfSentence);
+        assertTrue(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc", mSpacingAndPunctuations, 2).mIsBeginningOfSentence);
         // The following tests reflect the current behavior of the function
         // RichInputConnection#getNthPreviousWord.
         // TODO: However at this time, the code does never go
@@ -169,23 +173,23 @@
         // this function if needed - especially since it does not seem very
         // logical. These tests are just there to catch any unintentional
         // changes in the behavior of the RichInputConnection#getPreviousWord method.
-        assertEquals(RichInputConnection.getNthPreviousWord(
-                "abc def ", mSpacingAndPunctuations, 2), "abc");
-        assertEquals(RichInputConnection.getNthPreviousWord(
-                "abc def.", mSpacingAndPunctuations, 2), "abc");
-        assertEquals(RichInputConnection.getNthPreviousWord(
-                "abc def .", mSpacingAndPunctuations, 2), "def");
-        assertNull(RichInputConnection.getNthPreviousWord(
-                "abc ", mSpacingAndPunctuations, 2));
+        assertEquals(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def ", mSpacingAndPunctuations, 2).mPrevWord, "abc");
+        assertEquals(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def.", mSpacingAndPunctuations, 2).mPrevWord, "abc");
+        assertEquals(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def .", mSpacingAndPunctuations, 2).mPrevWord, "def");
+        assertEquals(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc ", mSpacingAndPunctuations, 2).mPrevWord, null);
 
-        assertEquals(RichInputConnection.getNthPreviousWord(
-                "abc def", mSpacingAndPunctuations, 1), "def");
-        assertEquals(RichInputConnection.getNthPreviousWord(
-                "abc def ", mSpacingAndPunctuations, 1), "def");
-        assertNull(RichInputConnection.getNthPreviousWord(
-                "abc def.", mSpacingAndPunctuations, 1));
-        assertNull(RichInputConnection.getNthPreviousWord(
-                "abc def .", mSpacingAndPunctuations, 1));
+        assertEquals(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def", mSpacingAndPunctuations, 1).mPrevWord, "def");
+        assertEquals(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def ", mSpacingAndPunctuations, 1).mPrevWord, "def");
+        assertNull(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def.", mSpacingAndPunctuations, 1).mPrevWord);
+        assertNull(RichInputConnection.getPrevWordsInfoFromNthPreviousWord(
+                "abc def .", mSpacingAndPunctuations, 1).mPrevWord);
     }
 
     /**
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
index 16e8b36..17e7185 100644
--- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -40,8 +40,8 @@
         final int[] COORDINATES_WITHIN_BMP =
                 CoordinateUtils.newCoordinateArray(CODEPOINTS_WITHIN_BMP.length,
                         Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-        final String PREVWORD = "prevword";
-        wc.setComposingWord(CODEPOINTS_WITHIN_BMP, COORDINATES_WITHIN_BMP, PREVWORD);
+        final PrevWordsInfo PREV_WORDS_INFO = new PrevWordsInfo("prevword");
+        wc.setComposingWord(CODEPOINTS_WITHIN_BMP, COORDINATES_WITHIN_BMP, PREV_WORDS_INFO);
         assertEquals(wc.size(), STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length()));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         wc.setCursorPositionWithinWord(2);
@@ -57,7 +57,7 @@
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         // Check the previous word is still there
-        assertEquals(PREVWORD, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO, wc.getPrevWordsInfoForSuggestion());
         // Move the cursor past the end of the word
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15));
@@ -74,7 +74,7 @@
                 CoordinateUtils.newCoordinateArray(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length,
                         Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                new PrevWordsInfo(null));
         assertEquals(wc.size(), CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length);
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         wc.setCursorPositionWithinWord(3);
@@ -85,46 +85,53 @@
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         assertNull(wc.getPrevWordsInfoForSuggestion().mPrevWord);
 
+        final PrevWordsInfo PREV_WORDS_INFO_STR_WITHIN_BMP = new PrevWordsInfo(STR_WITHIN_BMP);
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                STR_WITHIN_BMP);
+                PREV_WORDS_INFO_STR_WITHIN_BMP);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
-        assertEquals(STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO_STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion());
 
+        final PrevWordsInfo PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR =
+                new PrevWordsInfo(STR_WITH_SUPPLEMENTARY_CHAR);
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                STR_WITH_SUPPLEMENTARY_CHAR);
+                PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
-        assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR,
+                wc.getPrevWordsInfoForSuggestion());
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                STR_WITHIN_BMP);
+                PREV_WORDS_INFO_STR_WITHIN_BMP);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1));
-        assertEquals(STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO_STR_WITHIN_BMP, wc.getPrevWordsInfoForSuggestion());
 
+
+        final PrevWordsInfo PREV_WORDS_INFO_NULL = new PrevWordsInfo(null);
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                PREV_WORDS_INFO_NULL);
         wc.setCursorPositionWithinWord(3);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9));
         assertNull(wc.getPrevWordsInfoForSuggestion().mPrevWord);
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                STR_WITH_SUPPLEMENTARY_CHAR);
+                PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10));
-        assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPrevWordsInfoForSuggestion().mPrevWord);
+        assertEquals(PREV_WORDS_INFO_STR_WITH_SUPPLEMENTARY_CHAR,
+                wc.getPrevWordsInfoForSuggestion());
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                PREV_WORDS_INFO_NULL);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11));
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                PREV_WORDS_INFO_NULL);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
 
         wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
-                null /* previousWord */);
+                PREV_WORDS_INFO_NULL);
         wc.setCursorPositionWithinWord(2);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
     }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index dab9a43..a04b810 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -19,6 +19,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.PtNode;
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
@@ -74,13 +75,13 @@
         for (final WordProperty wordProperty : dict) {
             // TODO: switch to addMultipleDictionaryEntries when they support shortcuts
             if (null == wordProperty.mShortcutTargets || wordProperty.mShortcutTargets.isEmpty()) {
-                binaryDict.addUnigramWord(wordProperty.mWord, wordProperty.getProbability(),
+                binaryDict.addUnigramEntry(wordProperty.mWord, wordProperty.getProbability(),
                         null /* shortcutTarget */, 0 /* shortcutProbability */,
                         wordProperty.mIsNotAWord, wordProperty.mIsBlacklistEntry,
                         0 /* timestamp */);
             } else {
                 for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
-                    binaryDict.addUnigramWord(wordProperty.mWord, wordProperty.getProbability(),
+                    binaryDict.addUnigramEntry(wordProperty.mWord, wordProperty.getProbability(),
                             shortcutTarget.mWord, shortcutTarget.getProbability(),
                             wordProperty.mIsNotAWord, wordProperty.mIsBlacklistEntry,
                             0 /* timestamp */);
@@ -93,8 +94,8 @@
         for (final WordProperty word0Property : dict) {
             if (null == word0Property.mBigrams) continue;
             for (final WeightedString word1 : word0Property.mBigrams) {
-                binaryDict.addBigramWords(word0Property.mWord, word1.mWord, word1.getProbability(),
-                        0 /* timestamp */);
+                binaryDict.addNgramEntry(new PrevWordsInfo(word0Property.mWord), word1.mWord,
+                        word1.getProbability(), 0 /* timestamp */);
                 if (binaryDict.needsToRunGC(true /* mindsBlockByGC */)) {
                     binaryDict.flushWithGC();
                 }
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index f2d7b76..bc86864 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.FileUtils;
@@ -109,11 +110,11 @@
     }
 
     private static void addToDict(final UserHistoryDictionary dict, final List<String> words) {
-        String prevWord = null;
+        PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null);
         for (String word : words) {
-            UserHistoryDictionary.addToDictionary(dict, prevWord, word, true,
+            UserHistoryDictionary.addToDictionary(dict, prevWordsInfo, word, true,
                     (int)TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
-            prevWord = word;
+            prevWordsInfo = new PrevWordsInfo(word);
         }
     }
 
@@ -260,10 +261,10 @@
         final UserHistoryDictionary dict =
                 PersonalizationHelper.getUserHistoryDictionary(getContext(), dummyLocale);
         dict.waitAllTasksForTests();
-        String prevWord = null;
+        PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null);
         for (final String word : words) {
-            UserHistoryDictionary.addToDictionary(dict, prevWord, word, true, mCurrentTime);
-            prevWord = word;
+            UserHistoryDictionary.addToDictionary(dict, prevWordsInfo, word, true, mCurrentTime);
+            prevWordsInfo = new PrevWordsInfo(word);
             dict.waitAllTasksForTests();
             assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word));
         }