Merge "Start-of-sentence should include newlines and non-period terminators."
diff --git a/java-overridable/src/com/android/inputmethod/latin/define/DecoderSpecificConstants.java b/java-overridable/src/com/android/inputmethod/latin/define/DecoderSpecificConstants.java
index a82abdb..7f57ce8 100644
--- a/java-overridable/src/com/android/inputmethod/latin/define/DecoderSpecificConstants.java
+++ b/java-overridable/src/com/android/inputmethod/latin/define/DecoderSpecificConstants.java
@@ -34,4 +34,5 @@
     public static final boolean SHOULD_VERIFY_CHECKSUM = true;
     public static final boolean SHOULD_USE_DICT_VERSION = true;
     public static final boolean SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION = false;
+    public static final boolean SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION = true;
 }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index daac5b5..7f4631b 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -58,7 +58,8 @@
     // Must be equal to CONFIDENCE_TO_AUTO_COMMIT in native/jni/src/defines.h
     private static final int CONFIDENCE_TO_AUTO_COMMIT = 1000000;
 
-    static final int DICTIONARY_MAX_WORD_LENGTH = 48;
+    public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
+    public static final int MAX_PREV_WORD_COUNT_FOR_N_GRAM = 3;
 
     @UsedForTesting
     public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index ba0f9b8..15a14e5 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -125,7 +125,8 @@
      */
     private void addNameLocked(final String name) {
         int len = StringUtils.codePointCount(name);
-        NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
+        NgramContext ngramContext = NgramContext.getEmptyPrevWordsContext(
+                BinaryDictionary.MAX_PREV_WORD_COUNT_FOR_N_GRAM);
         // TODO: Better tokenization for non-Latin writing systems
         for (int i = 0; i < len; i++) {
             if (Character.isLetter(name.codePointAt(i))) {
diff --git a/java/src/com/android/inputmethod/latin/NgramContext.java b/java/src/com/android/inputmethod/latin/NgramContext.java
index aeeff61..9682fb8 100644
--- a/java/src/com/android/inputmethod/latin/NgramContext.java
+++ b/java/src/com/android/inputmethod/latin/NgramContext.java
@@ -43,6 +43,10 @@
 
     public static final String CONTEXT_SEPARATOR = " ";
 
+    public static NgramContext getEmptyPrevWordsContext(int maxPrevWordCount) {
+        return new NgramContext(maxPrevWordCount, WordInfo.EMPTY_WORD_INFO);
+    }
+
     /**
      * Word information used to represent previous words information.
      */
@@ -102,10 +106,17 @@
     private final WordInfo[] mPrevWordsInfo;
     private final int mPrevWordsCount;
 
+    private final int mMaxPrevWordCount;
+
     // Construct from the previous word information.
     public NgramContext(final WordInfo... prevWordsInfo) {
+        this(DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM, prevWordsInfo);
+    }
+
+    public NgramContext(final int maxPrevWordCount, final WordInfo... prevWordsInfo) {
         mPrevWordsInfo = prevWordsInfo;
         mPrevWordsCount = prevWordsInfo.length;
+        mMaxPrevWordCount = maxPrevWordCount;
     }
 
     /**
@@ -113,12 +124,11 @@
      */
     @Nonnull
     public NgramContext getNextNgramContext(final WordInfo wordInfo) {
-        final int nextPrevWordCount = Math.min(
-                DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM, mPrevWordsCount + 1);
+        final int nextPrevWordCount = Math.min(mMaxPrevWordCount, mPrevWordsCount + 1);
         final WordInfo[] prevWordsInfo = new WordInfo[nextPrevWordCount];
         prevWordsInfo[0] = wordInfo;
         System.arraycopy(mPrevWordsInfo, 0, prevWordsInfo, 1, nextPrevWordCount - 1);
-        return new NgramContext(prevWordsInfo);
+        return new NgramContext(mMaxPrevWordCount, prevWordsInfo);
     }
 
 
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 660b64c..8562acd 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -19,6 +19,7 @@
 import android.text.TextUtils;
 
 import static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION;
+import static com.android.inputmethod.latin.define.DecoderSpecificConstants.SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION;
 
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -311,8 +312,10 @@
             }
         }
 
-        if (suggestionsContainer.size() > 1 && TextUtils.equals(suggestionsContainer.get(0).mWord,
-                wordComposer.getRejectedBatchModeSuggestion())) {
+        if (SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION
+                && suggestionsContainer.size() > 1
+                && TextUtils.equals(suggestionsContainer.get(0).mWord,
+                   wordComposer.getRejectedBatchModeSuggestion())) {
             final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
             suggestionsContainer.add(1, rejected);
         }
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 54ee68d..cbf0829 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -20,6 +20,7 @@
 
 import com.android.inputmethod.annotations.ExternallyReferenced;
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 import com.android.inputmethod.latin.NgramContext;
@@ -98,7 +99,7 @@
     public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
             @Nonnull final NgramContext ngramContext, final String word, final boolean isValid,
             final int timestamp) {
-        if (word.length() > DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH) {
+        if (word.length() > BinaryDictionary.DICTIONARY_MAX_WORD_LENGTH) {
             return;
         }
         userHistoryDictionary.updateEntriesForWord(ngramContext, word,
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
index b1b4813..eb94e22 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java
@@ -16,17 +16,33 @@
 
 package com.android.inputmethod.latin;
 
-import android.test.MoreAsserts;
+import static android.test.MoreAsserts.assertNotEqual;
+
 import android.test.suitebuilder.annotation.LargeTest;
 import android.text.TextUtils;
 import android.view.inputmethod.BaseInputConnection;
 
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
 import com.android.inputmethod.latin.settings.Settings;
 
 @LargeTest
 public class InputLogicTests extends InputTestsBase {
 
+    private boolean mNextWordPrediction;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mNextWordPrediction = getBooleanPreference(Settings.PREF_BIGRAM_PREDICTIONS, true);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        setBooleanPreference(Settings.PREF_BIGRAM_PREDICTIONS, mNextWordPrediction, true);
+        super.tearDown();
+    }
+
     public void testTypeWord() {
         final String WORD_TO_TYPE = "abcd";
         type(WORD_TO_TYPE);
@@ -494,24 +510,19 @@
 
     public void testPredictionsWithDoubleSpaceToPeriod() {
         mLatinIME.clearPersonalizedDictionariesForTest();
-        final String WORD_TO_TYPE = "Barack ";
+        final String WORD_TO_TYPE = "Barack  ";
         type(WORD_TO_TYPE);
         sleep(DELAY_TO_WAIT_FOR_PREDICTIONS_MILLIS);
         runMessages();
-        // No need to test here, testPredictionsAfterSpace is testing it already
-        type(" ");
-        sleep(DELAY_TO_WAIT_FOR_PREDICTIONS_MILLIS);
-        runMessages();
-        // Test the predictions have been cleared
-        SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
-        assertEquals("predictions cleared after double-space-to-period", suggestedWords.size(), 0);
+
         type(Constants.CODE_DELETE);
         sleep(DELAY_TO_WAIT_FOR_PREDICTIONS_MILLIS);
         runMessages();
-        // Test the first prediction is displayed
+
+        SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
         suggestedWords = mLatinIME.getSuggestedWordsForTest();
         assertEquals("predictions after cancel double-space-to-period", "Obama",
-                suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null);
+                mLatinIME.getSuggestedWordsForTest().getWord(0));
     }
 
     public void testPredictionsAfterManualPick() {
@@ -533,15 +544,9 @@
         type(WORD_TO_TYPE);
         sleep(DELAY_TO_WAIT_FOR_PREDICTIONS_MILLIS);
         runMessages();
-        SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
-        assertEquals("No prediction after period after inputting once.", 0, suggestedWords.size());
 
-        type(WORD_TO_TYPE);
-        sleep(DELAY_TO_WAIT_FOR_PREDICTIONS_MILLIS);
-        runMessages();
-        suggestedWords = mLatinIME.getSuggestedWordsForTest();
-        assertEquals("Beginning-of-Sentence prediction after inputting 2 times.", "Barack",
-                suggestedWords.size() > 0 ? suggestedWords.getWord(0) : null);
+        SuggestedWords suggestedWords = mLatinIME.getSuggestedWordsForTest();
+        assertFalse(mLatinIME.getSuggestedWordsForTest().isEmpty());
     }
 
     public void testPredictionsAfterRecorrection() {
@@ -668,8 +673,11 @@
         type(Constants.CODE_DELETE);
         assertEquals("gesture then backspace", "", mEditText.getText().toString());
         gesture("this");
-        MoreAsserts.assertNotEqual("gesture twice the same thing", "this",
-                mEditText.getText().toString());
+        if (DecoderSpecificConstants.SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION) {
+            assertNotEqual("this", mEditText.getText().toString());
+        } else {
+            assertEquals("this", mEditText.getText().toString());
+        }
     }
 
     private void typeOrGestureWordAndPutCursorInside(final boolean gesture, final String word,
@@ -736,12 +744,13 @@
         ensureComposingSpanPos("space while in the middle of a word cancels composition", -1, -1);
     }
 
+    // TODO: Verify this works when we return FIGS language models to the APK.
     public void testAutoCorrectForFrench() {
         final String STRING_TO_TYPE = "irq ";
-        final String EXPECTED_RESULT = "ira ";
-        changeLanguage("fr");
+        final String EXPECTED_RESULT = "ir a ";
+        changeLanguage("es");
         type(STRING_TO_TYPE);
-        assertEquals("simple auto-correct for French", EXPECTED_RESULT,
+        assertEquals("simple auto-correct for Spanish", EXPECTED_RESULT,
                 mEditText.getText().toString());
     }
 
@@ -771,6 +780,8 @@
     }
 
     public void testWordThenSpaceThenPunctuationFromStripTwiceForFrench() {
+        setBooleanPreference(Settings.PREF_BIGRAM_PREDICTIONS, false, true);
+
         final String WORD_TO_TYPE = "test ";
         final String PUNCTUATION_FROM_STRIP = "!";
         final String EXPECTED_RESULT = "test !!";
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 6ae38c6..ab4060a 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -164,6 +164,11 @@
         return previousSetting;
     }
 
+    protected boolean getBooleanPreference(final String key, final boolean defaultValue) {
+        return PreferenceManager.getDefaultSharedPreferences(mLatinIME)
+                .getBoolean(key, defaultValue);
+    }
+
     protected String setStringPreference(final String key, final String value,
             final String defaultValue) {
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTestsHelper.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTestsHelper.java
index 94105aa..4b7b9bc 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTestsHelper.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTestsHelper.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 
+import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.NgramContext;
 import com.android.inputmethod.latin.NgramContext.WordInfo;
 import com.android.inputmethod.latin.common.FileUtils;
@@ -98,7 +99,8 @@
 
     private static void addWordsToDictionary(final UserHistoryDictionary dict,
             final List<String> words, final int timestamp) {
-        NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
+        NgramContext ngramContext = NgramContext.getEmptyPrevWordsContext(
+                BinaryDictionary.MAX_PREV_WORD_COUNT_FOR_N_GRAM);
         for (final String word : words) {
             UserHistoryDictionary.addToDictionary(dict, ngramContext, word, true, timestamp);
             ngramContext = ngramContext.getNextNgramContext(new WordInfo(word));