Merge "Improve bigram probability computation for decaying dicts."
diff --git a/dictionaries/de_wordlist.combined.gz b/dictionaries/de_wordlist.combined.gz
index 8065e04..803211c 100644
--- a/dictionaries/de_wordlist.combined.gz
+++ b/dictionaries/de_wordlist.combined.gz
Binary files differ
diff --git a/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java b/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java
index 9d7258d..5738ea2 100644
--- a/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java
+++ b/java-overridable/src/com/android/inputmethod/latin/define/ProductionFlags.java
@@ -24,14 +24,6 @@
     public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false;
 
     /**
-     * When true, enable {@link InputMethodService#onUpdateCursorAnchorInfo} callback via
-     * {@link InputConnection#requestUpdateCursorAnchorInfo}. This flag has no effect in API
-     * Level 20 and prior. In general, this callback provides detailed positional information,
-     * even though an explicit support is required by the editor.
-     */
-    public static final boolean ENABLE_CURSOR_ANCHOR_INFO_CALLBACK = true;
-
-    /**
      * Include all suggestions from all dictionaries in {@link SuggestedWords#mRawSuggestions}.
      */
     public static final boolean INCLUDE_RAW_SUGGESTIONS = false;
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 25fa0f9..ee1cef6 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -89,8 +89,7 @@
 
         <activity android:name=".settings.SettingsActivity"
                 android:theme="@style/platformSettingsTheme"
-                android:label="@string/english_ime_settings"
-                android:uiOptions="splitActionBarWhenNarrow">
+                android:label="@string/english_ime_settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
diff --git a/java/res/raw/main_de.dict b/java/res/raw/main_de.dict
index 4a8cdf1..45b2883 100644
--- a/java/res/raw/main_de.dict
+++ b/java/res/raw/main_de.dict
Binary files differ
diff --git a/java/res/raw/setup_welcome_image.png b/java/res/raw/setup_welcome_image.png
index 98e7313..b726181 100644
--- a/java/res/raw/setup_welcome_image.png
+++ b/java/res/raw/setup_welcome_image.png
Binary files differ
diff --git a/java/res/raw/setup_welcome_video.mp4 b/java/res/raw/setup_welcome_video.mp4
index 224bf25..09be565 100644
--- a/java/res/raw/setup_welcome_video.mp4
+++ b/java/res/raw/setup_welcome_video.mp4
Binary files differ
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index c33c015..27db9b8 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -23,10 +23,11 @@
 import android.text.TextUtils;
 import android.text.style.SuggestionSpan;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.define.DebugFlags;
 import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
+import com.android.inputmethod.latin.define.DebugFlags;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -51,12 +52,14 @@
         // This utility class is not publicly instantiable.
     }
 
+    @UsedForTesting
     public static CharSequence getTextWithAutoCorrectionIndicatorUnderline(
             final Context context, final String text) {
         if (TextUtils.isEmpty(text) || OBJ_FLAG_AUTO_CORRECTION == null) {
             return text;
         }
         final Spannable spannable = new SpannableString(text);
+        // TODO: Set locale if it is feasible.
         final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
                 new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION,
                 SuggestionSpanPickedNotificationReceiver.class);
@@ -65,6 +68,7 @@
         return spannable;
     }
 
+    @UsedForTesting
     public static CharSequence getTextWithSuggestionSpan(final Context context,
             final String pickedWord, final SuggestedWords suggestedWords) {
         if (TextUtils.isEmpty(pickedWord) || suggestedWords.isEmpty()
@@ -86,6 +90,7 @@
                 suggestionsList.add(word.toString());
             }
         }
+        // TODO: Set locale if it is feasible.
         final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
                 suggestionsList.toArray(new String[suggestionsList.size()]), 0 /* flags */,
                 SuggestionSpanPickedNotificationReceiver.class);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 86c265f..10dea74 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -42,6 +42,8 @@
 import java.util.Locale;
 import java.util.Map;
 
+import javax.annotation.Nonnull;
+
 /**
  * Implements a static, compacted, binary dictionary of standard words.
  */
@@ -495,8 +497,7 @@
     }
 
     // Update entries for the word occurrence with the ngramContext.
-    @UsedForTesting
-    public boolean updateEntriesForWordWithNgramContext(final NgramContext ngramContext,
+    public boolean updateEntriesForWordWithNgramContext(@Nonnull final NgramContext ngramContext,
             final String word, final boolean isValidWord, final int count, final int timestamp) {
         if (TextUtils.isEmpty(word)) {
             return false;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 1bdadc3..d24f80a 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -46,6 +46,8 @@
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+import javax.annotation.Nonnull;
+
 /**
  * Abstract base class for an expandable dictionary that can be created and updated dynamically
  * during runtime. When updated it automatically generates a new binary dictionary to handle future
@@ -292,13 +294,9 @@
         }
     }
 
-    /**
-     * Adds unigram information of a word to the dictionary. May overwrite an existing entry.
-     */
-    public void addUnigramEntryWithCheckingDistracter(final String word, final int frequency,
-            final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
-            final boolean isBlacklisted, final int timestamp,
-            final DistracterFilter distracterFilter) {
+    private void updateDictionaryWithWriteLockIfWordIsNotADistracter(
+            @Nonnull final Runnable updateTask,
+            @Nonnull final String word, @Nonnull final DistracterFilter distracterFilter) {
         reloadDictionaryIfRequired();
         asyncPreCheckAndExecuteTaskWithWriteLock(
                 new Callable<Boolean>() {
@@ -315,12 +313,27 @@
                             return;
                         }
                         runGCIfRequiredLocked(true /* mindsBlockByGC */);
-                        addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq,
-                                isNotAWord, isBlacklisted, timestamp);
+                        updateTask.run();
                     }
                 });
     }
 
+    /**
+     * Adds unigram information of a word to the dictionary. May overwrite an existing entry.
+     */
+    public void addUnigramEntryWithCheckingDistracter(final String word, final int frequency,
+            final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
+            final boolean isBlacklisted, final int timestamp,
+            @Nonnull final DistracterFilter distracterFilter) {
+        updateDictionaryWithWriteLockIfWordIsNotADistracter(new Runnable() {
+            @Override
+            public void run() {
+                addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq,
+                        isNotAWord, isBlacklisted, timestamp);
+            }
+        }, word, distracterFilter);
+    }
+
     protected void addUnigramLocked(final String word, final int frequency,
             final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
@@ -354,7 +367,7 @@
     /**
      * Adds n-gram information of a word to the dictionary. May overwrite an existing entry.
      */
-    public void addNgramEntry(final NgramContext ngramContext, final String word,
+    public void addNgramEntry(@Nonnull final NgramContext ngramContext, final String word,
             final int frequency, final int timestamp) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
@@ -369,7 +382,7 @@
         });
     }
 
-    protected void addNgramEntryLocked(final NgramContext ngramContext, final String word,
+    protected void addNgramEntryLocked(@Nonnull final NgramContext ngramContext, final String word,
             final int frequency, final int timestamp) {
         if (!mBinaryDictionary.addNgramEntry(ngramContext, word, frequency, timestamp)) {
             if (DEBUG) {
@@ -383,7 +396,8 @@
      * Dynamically remove the n-gram entry in the dictionary.
      */
     @UsedForTesting
-    public void removeNgramDynamically(final NgramContext ngramContext, final String word) {
+    public void removeNgramDynamically(@Nonnull final NgramContext ngramContext,
+            final String word) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
             @Override
@@ -402,6 +416,26 @@
         });
     }
 
+    /**
+     * Update dictionary for the word with the ngramContext if the word is not a distracter.
+     */
+    public void updateEntriesForWordWithCheckingDistracter(@Nonnull final NgramContext ngramContext,
+            final String word, final boolean isValidWord, final int count, final int timestamp,
+            @Nonnull final DistracterFilter distracterFilter) {
+        updateDictionaryWithWriteLockIfWordIsNotADistracter(new Runnable() {
+            @Override
+            public void run() {
+                if (!mBinaryDictionary.updateEntriesForWordWithNgramContext(ngramContext, word,
+                        isValidWord, count, timestamp)) {
+                    if (DEBUG) {
+                        Log.e(TAG, "Cannot update counter. word: " + word
+                                + " context: "+ ngramContext.toString());
+                    }
+                }
+            }
+        }, word, distracterFilter);
+    }
+
     public interface AddMultipleDictionaryEntriesCallback {
         public void onFinished();
     }
@@ -410,7 +444,7 @@
      * Dynamically add multiple entries to the dictionary.
      */
     public void addMultipleDictionaryEntriesDynamically(
-            final ArrayList<LanguageModelParam> languageModelParams,
+            @Nonnull final ArrayList<LanguageModelParam> languageModelParams,
             final AddMultipleDictionaryEntriesCallback callback) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index be2efb2..1cd285c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -755,12 +755,12 @@
         if (prevExtractEditText == nextExtractEditText) {
             return;
         }
-        if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK && prevExtractEditText != null) {
+        if (prevExtractEditText != null) {
             prevExtractEditText.getViewTreeObserver().removeOnPreDrawListener(
                     mExtractTextViewPreDrawListener);
         }
         mExtractEditText = nextExtractEditText;
-        if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK && mExtractEditText != null) {
+        if (mExtractEditText != null) {
             mExtractEditText.getViewTreeObserver().addOnPreDrawListener(
                     mExtractTextViewPreDrawListener);
         }
@@ -776,8 +776,7 @@
             };
 
     private void onExtractTextViewPreDraw() {
-        if (!ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK || !isFullscreenMode()
-                || mExtractEditText == null) {
+        if (!isFullscreenMode() || mExtractEditText == null) {
             return;
         }
         final CursorAnchorInfo info = CursorAnchorInfoUtils.getCursorAnchorInfo(mExtractEditText);
@@ -1060,7 +1059,7 @@
     // We cannot mark this method as @Override until new SDK becomes publicly available.
     // @Override
     public void onUpdateCursorAnchorInfo(final CursorAnchorInfo info) {
-        if (!ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK || isFullscreenMode()) {
+        if (isFullscreenMode()) {
             return;
         }
         mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
diff --git a/java/src/com/android/inputmethod/latin/NgramContext.java b/java/src/com/android/inputmethod/latin/NgramContext.java
index 6d43858..a02531c 100644
--- a/java/src/com/android/inputmethod/latin/NgramContext.java
+++ b/java/src/com/android/inputmethod/latin/NgramContext.java
@@ -158,11 +158,6 @@
         }
     }
 
-    public NgramContext getTrimmedNgramContext(final int maxPrevWordCount) {
-        final int newSize = Math.min(maxPrevWordCount, mPrevWordsCount);
-        return new NgramContext(this /* prevWordsInfo */, newSize);
-    }
-
     public int getPrevWordCount() {
         return mPrevWordsCount;
     }
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 1b1d5e7..f67a9a6 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -169,14 +169,11 @@
             mInputLogicHandler.reset();
         }
 
-        if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) {
-            // AcceptTypedWord feature relies on CursorAnchorInfo.
-            if (settingsValues.mShouldShowUiToAcceptTypedWord) {
-                mConnection.requestCursorUpdates(true /* enableMonitor */,
-                        true /* requestImmediateCallback */);
-            }
-            mTextDecorator.reset();
+        if (settingsValues.mShouldShowUiToAcceptTypedWord) {
+            mConnection.requestCursorUpdates(true /* enableMonitor */,
+                    true /* requestImmediateCallback */);
         }
+        mTextDecorator.reset();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index d616846..5976154 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -65,34 +65,7 @@
         if (word.length() > Constants.DICTIONARY_MAX_WORD_LENGTH) {
             return;
         }
-        final int frequency = isValid ?
-                FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
-        userHistoryDictionary.addUnigramEntryWithCheckingDistracter(word, frequency,
-                null /* shortcutTarget */, 0 /* shortcutFreq */, false /* isNotAWord */,
-                false /* isBlacklisted */, timestamp, distracterFilter);
-
-        final boolean isBeginningOfSentenceContext = ngramContext.isBeginningOfSentenceContext();
-        final NgramContext ngramContextToBeSaved =
-                ngramContext.getTrimmedNgramContext(SUPPORTED_NGRAM - 1);
-        for (int i = 0; i < ngramContextToBeSaved.getPrevWordCount(); i++) {
-            final CharSequence prevWord = ngramContextToBeSaved.getNthPrevWord(1 /* n */);
-            if (prevWord == null || (prevWord.length() > Constants.DICTIONARY_MAX_WORD_LENGTH)) {
-                return;
-            }
-            // Do not insert a word as a bigram of itself
-            if (i == 0 && TextUtils.equals(word, prevWord)) {
-                return;
-            }
-            if (isBeginningOfSentenceContext) {
-                // Beginning-of-Sentence n-gram entry is added as an n-gram entry of an OOV word.
-                userHistoryDictionary.addNgramEntry(
-                        ngramContextToBeSaved.getTrimmedNgramContext(i + 1), word,
-                        FREQUENCY_FOR_WORDS_NOT_IN_DICTS, timestamp);
-            } else {
-                userHistoryDictionary.addNgramEntry(
-                        ngramContextToBeSaved.getTrimmedNgramContext(i + 1), word, frequency,
-                        timestamp);
-            }
-        }
+        userHistoryDictionary.updateEntriesForWordWithCheckingDistracter(ngramContext, word,
+                isValid, 1 /* count */, timestamp, distracterFilter);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
index 2174f52..c633fc1 100644
--- a/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/CustomInputStyleSettingsFragment.java
@@ -33,7 +33,6 @@
 import android.support.v4.view.ViewCompat;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -80,25 +79,26 @@
             "is_subtype_enabler_notification_dialog_open";
     private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler";
 
-    static final class SubtypeLocaleItem extends Pair<String, String>
-            implements Comparable<SubtypeLocaleItem> {
-        public SubtypeLocaleItem(final String localeString, final String displayName) {
-            super(localeString, displayName);
+    static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> {
+        public final String mLocaleString;
+        private final String mDisplayName;
+
+        public SubtypeLocaleItem(final InputMethodSubtype subtype) {
+            mLocaleString = subtype.getLocale();
+            mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(
+                    mLocaleString);
         }
 
-        public SubtypeLocaleItem(final String localeString) {
-            this(localeString,
-                    SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(localeString));
-        }
-
+        // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
+        // to get display name.
         @Override
         public String toString() {
-            return second;
+            return mDisplayName;
         }
 
         @Override
         public int compareTo(final SubtypeLocaleItem o) {
-            return first.compareTo(o.first);
+            return mLocaleString.compareTo(o.mLocaleString);
         }
     }
 
@@ -121,32 +121,28 @@
                             SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
                 }
                 if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) {
-                    items.add(createItem(context, subtype.getLocale()));
+                    items.add(new SubtypeLocaleItem(subtype));
                 }
             }
             // TODO: Should filter out already existing combinations of locale and layout.
             addAll(items);
         }
-
-        public static SubtypeLocaleItem createItem(final Context context,
-                final String localeString) {
-            if (localeString.equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
-                final String displayName = context.getString(R.string.subtype_no_language);
-                return new SubtypeLocaleItem(localeString, displayName);
-            }
-            return new SubtypeLocaleItem(localeString);
-        }
     }
 
-    static final class KeyboardLayoutSetItem extends Pair<String, String> {
+    static final class KeyboardLayoutSetItem {
+        public final String mLayoutName;
+        private final String mDisplayName;
+
         public KeyboardLayoutSetItem(final InputMethodSubtype subtype) {
-            super(SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype),
-                    SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype));
+            mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
+            mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
         }
 
+        // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
+        // to get display name.
         @Override
         public String toString() {
-            return second;
+            return mDisplayName;
         }
     }
 
@@ -255,7 +251,6 @@
 
         @Override
         protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
-            final Context context = builder.getContext();
             builder.setCancelable(true).setOnCancelListener(this);
             if (isIncomplete()) {
                 builder.setPositiveButton(R.string.add, this)
@@ -264,8 +259,7 @@
                 builder.setPositiveButton(R.string.save, this)
                         .setNeutralButton(android.R.string.cancel, this)
                         .setNegativeButton(R.string.remove, this);
-                final SubtypeLocaleItem localeItem = SubtypeLocaleAdapter.createItem(
-                        context, mSubtype.getLocale());
+                final SubtypeLocaleItem localeItem = new SubtypeLocaleItem(mSubtype);
                 final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype);
                 setSpinnerPosition(mSubtypeLocaleSpinner, localeItem);
                 setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem);
@@ -303,7 +297,7 @@
                         (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
                 final InputMethodSubtype subtype =
                         AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
-                                locale.first, layout.first);
+                                locale.mLocaleString, layout.mLayoutName);
                 setSubtype(subtype);
                 notifyChanged();
                 if (isEditing) {
@@ -469,8 +463,6 @@
                 KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN)) {
             mSubtypePreferenceKeyForSubtypeEnabler = savedInstanceState.getString(
                     KEY_SUBTYPE_FOR_SUBTYPE_ENABLER);
-            final SubtypePreference subtypePref = (SubtypePreference)findPreference(
-                    mSubtypePreferenceKeyForSubtypeEnabler);
             mSubtypeEnablerNotificationDialog = createDialog();
             mSubtypeEnablerNotificationDialog.show();
         }
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index b770ea5..7607429 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -17,12 +17,8 @@
 package com.android.inputmethod.latin.setup;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
-import android.provider.Settings;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
 
 public final class SetupActivity extends Activity {
     @Override
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index e455e53..54562f3 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -46,6 +46,8 @@
 public final class SetupWizardActivity extends Activity implements View.OnClickListener {
     static final String TAG = SetupWizardActivity.class.getSimpleName();
 
+    // For debugging purpose.
+    private static final boolean FORCE_TO_SHOW_WELCOME_SCREEN = false;
     private static final boolean ENABLE_WELCOME_VIDEO = true;
 
     private InputMethodManager mImm;
@@ -304,6 +306,9 @@
 
     private int determineSetupStepNumber() {
         mHandler.cancelPollingImeSettings();
+        if (FORCE_TO_SHOW_WELCOME_SCREEN) {
+            return STEP_1;
+        }
         if (!UncachedInputMethodManagerUtils.isThisImeEnabled(this, mImm)) {
             return STEP_1;
         }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
index e0c3af8..36eafa1 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
@@ -454,7 +454,10 @@
         AKLOGE("Cannot update unigarm entry in updateCounter().");
         return false;
     }
-    const NgramProperty ngramProperty(wordCodePoints.toVector(), probability, historicalInfo);
+    const int probabilityForNgram = prevWordsInfo->isNthPrevWordBeginningOfSentence(1 /* n */)
+            ? NOT_A_PROBABILITY : probability;
+    const NgramProperty ngramProperty(wordCodePoints.toVector(), probabilityForNgram,
+            historicalInfo);
     if (!addNgramEntry(prevWordsInfo, &ngramProperty)) {
         AKLOGE("Cannot update unigarm entry in updateCounter().");
         return false;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index 41b109f..036197c 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -378,7 +378,10 @@
         AKLOGE("Cannot update unigarm entry in updateCounter().");
         return false;
     }
-    const NgramProperty ngramProperty(wordCodePoints.toVector(), probability, historicalInfo);
+    const int probabilityForNgram = prevWordsInfo->isNthPrevWordBeginningOfSentence(1 /* n */)
+            ? NOT_A_PROBABILITY : probability;
+    const NgramProperty ngramProperty(wordCodePoints.toVector(), probabilityForNgram,
+            historicalInfo);
     for (size_t i = 1; i <= prevWordsInfo->getPrevWordCount(); ++i) {
         const PrevWordsInfo trimmedPrevWordsInfo(prevWordsInfo->getTrimmedPrevWordsInfo(i));
         if (!addNgramEntry(&trimmedPrevWordsInfo, &ngramProperty)) {
diff --git a/tests/src/com/android/inputmethod/compat/SuggestionSpanUtilsTest.java b/tests/src/com/android/inputmethod/compat/SuggestionSpanUtilsTest.java
new file mode 100644
index 0000000..a6b3af4
--- /dev/null
+++ b/tests/src/com/android/inputmethod/compat/SuggestionSpanUtilsTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+
+@SmallTest
+public class SuggestionSpanUtilsTest extends AndroidTestCase {
+
+    /**
+     * Helper method to create a dummy {@link SuggestedWordInfo}.
+     *
+     * @param kindAndFlags the kind and flags to be used to create {@link SuggestedWordInfo}.
+     * @param word the word to be used to create {@link SuggestedWordInfo}.
+     * @return a new instance of {@link SuggestedWordInfo}.
+     */
+    private static SuggestedWordInfo createWordInfo(final String word, final int kindAndFlags) {
+        return new SuggestedWordInfo(word, 1 /* score */, kindAndFlags, null /* sourceDict */,
+                SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+                SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
+    }
+
+    private static void assertNotSuggestionSpan(final String expectedText,
+            final CharSequence actualText) {
+        assertTrue(TextUtils.equals(expectedText, actualText));
+        if (!(actualText instanceof Spanned)) {
+            return;
+        }
+        final Spanned spanned = (Spanned)actualText;
+        final SuggestionSpan[] suggestionSpans = spanned.getSpans(0, spanned.length(),
+                SuggestionSpan.class);
+        assertEquals(0, suggestionSpans.length);
+    }
+
+    private static void assertSuggestionSpan(final String expectedText,
+            final int reuiredSuggestionSpanFlags, final int requiredSpanFlags,
+            final String[] expectedSuggestions,
+            final CharSequence actualText) {
+        assertTrue(TextUtils.equals(expectedText, actualText));
+        assertTrue(actualText instanceof Spanned);
+        final Spanned spanned = (Spanned)actualText;
+        final SuggestionSpan[] suggestionSpans = spanned.getSpans(0, spanned.length(),
+                SuggestionSpan.class);
+        assertEquals(1, suggestionSpans.length);
+        final SuggestionSpan suggestionSpan = suggestionSpans[0];
+        if (reuiredSuggestionSpanFlags != 0) {
+            assertTrue((suggestionSpan.getFlags() & reuiredSuggestionSpanFlags) != 0);
+        }
+        if (requiredSpanFlags != 0) {
+            assertTrue((spanned.getSpanFlags(suggestionSpan) & requiredSpanFlags) != 0);
+        }
+        if (expectedSuggestions != null) {
+            final String[] actualSuggestions = suggestionSpan.getSuggestions();
+            assertEquals(expectedSuggestions.length, actualSuggestions.length);
+            for (int i = 0; i < expectedSuggestions.length; ++i) {
+                assertEquals(expectedSuggestions[i], actualSuggestions[i]);
+            }
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+    public void testGetTextWithAutoCorrectionIndicatorUnderline() {
+        final String ORIGINAL_TEXT = "Hey!";
+        final CharSequence text = SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
+                getContext(), ORIGINAL_TEXT);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+            assertNotSuggestionSpan(ORIGINAL_TEXT, text);
+            return;
+        }
+
+        assertSuggestionSpan(ORIGINAL_TEXT,
+                SuggestionSpan.FLAG_AUTO_CORRECTION /* reuiredSuggestionSpanFlags */,
+                Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* requiredSpanFlags */,
+                new String[]{}, text);
+    }
+
+    public void testGetTextWithSuggestionSpan() {
+        final SuggestedWordInfo predicition1 =
+                createWordInfo("Quality", SuggestedWordInfo.KIND_PREDICTION);
+        final SuggestedWordInfo predicition2 =
+                createWordInfo("Speed", SuggestedWordInfo.KIND_PREDICTION);
+        final SuggestedWordInfo predicition3 =
+                createWordInfo("Price", SuggestedWordInfo.KIND_PREDICTION);
+
+        final SuggestedWordInfo typed =
+                createWordInfo("Hey", SuggestedWordInfo.KIND_TYPED);
+
+        final SuggestedWordInfo[] corrections =
+                new SuggestedWordInfo[SuggestionSpan.SUGGESTIONS_MAX_SIZE * 2];
+        for (int i = 0; i < corrections.length; ++i) {
+            corrections[i] = createWordInfo("correction" + i, SuggestedWordInfo.KIND_CORRECTION);
+        }
+
+        // SuggestionSpan will not be attached when {@link SuggestedWords#INPUT_STYLE_PREDICTION}
+        // is specified.
+        {
+            final SuggestedWords predictedWords = new SuggestedWords(
+                    new ArrayList<>(Arrays.asList(predicition1, predicition2, predicition3)),
+                    null /* rawSuggestions */,
+                    false /* typedWordValid */,
+                    false /* willAutoCorrect */,
+                    false /* isObsoleteSuggestions */,
+                    SuggestedWords.INPUT_STYLE_PREDICTION);
+            final String PICKED_WORD = predicition2.mWord;
+            assertNotSuggestionSpan(
+                    PICKED_WORD,
+                    SuggestionSpanUtils.getTextWithSuggestionSpan(getContext(), PICKED_WORD,
+                            predictedWords));
+        }
+
+        final ArrayList<SuggestedWordInfo> suggestedWordList = new ArrayList<>();
+        suggestedWordList.add(typed);
+        suggestedWordList.add(predicition1);
+        suggestedWordList.add(predicition2);
+        suggestedWordList.add(predicition3);
+        suggestedWordList.addAll(Arrays.asList(corrections));
+        final SuggestedWords typedAndCollectedWords = new SuggestedWords(
+                suggestedWordList,
+                null /* rawSuggestions */,
+                false /* typedWordValid */,
+                false /* willAutoCorrect */,
+                false /* isObsoleteSuggestions */,
+                SuggestedWords.INPUT_STYLE_TYPING);
+
+        for (final SuggestedWordInfo pickedWord : suggestedWordList) {
+            final String PICKED_WORD = pickedWord.mWord;
+
+            final ArrayList<String> expectedSuggestions = new ArrayList<>();
+            for (SuggestedWordInfo suggestedWordInfo : suggestedWordList) {
+                if (expectedSuggestions.size() >= SuggestionSpan.SUGGESTIONS_MAX_SIZE) {
+                    break;
+                }
+                if (suggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_PREDICTION)) {
+                    // Currently predictions are not filled into SuggestionSpan.
+                    continue;
+                }
+                final String suggestedWord = suggestedWordInfo.mWord;
+                if (TextUtils.equals(PICKED_WORD, suggestedWord)) {
+                    // Typed word itself is not added to SuggestionSpan.
+                    continue;
+                }
+                expectedSuggestions.add(suggestedWord);
+            }
+
+            assertSuggestionSpan(
+                    PICKED_WORD,
+                    0 /* reuiredSuggestionSpanFlags */,
+                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* requiredSpanFlags */,
+                    expectedSuggestions.toArray(new String[expectedSuggestions.size()]),
+                    SuggestionSpanUtils.getTextWithSuggestionSpan(getContext(), PICKED_WORD,
+                            typedAndCollectedWords));
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
index c3c2bb2..0e58b72 100644
--- a/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictionaryDecayingTests.java
@@ -611,17 +611,23 @@
                 mCurrentTime);
         final NgramContext beginningOfSentenceContext = NgramContext.BEGINNING_OF_SENTENCE;
         onInputWordWithBeginningOfSentenceContext(binaryDictionary, "aaa", true /* isValidWord */);
+        assertFalse(binaryDictionary.isValidNgram(beginningOfSentenceContext, "aaa"));
+        onInputWordWithBeginningOfSentenceContext(binaryDictionary, "aaa", true /* isValidWord */);
         assertTrue(binaryDictionary.isValidNgram(beginningOfSentenceContext, "aaa"));
         onInputWordWithBeginningOfSentenceContext(binaryDictionary, "aaa", true /* isValidWord */);
         onInputWordWithBeginningOfSentenceContext(binaryDictionary, "bbb", true /* isValidWord */);
+        assertFalse(binaryDictionary.isValidNgram(beginningOfSentenceContext, "bbb"));
         onInputWordWithBeginningOfSentenceContext(binaryDictionary, "bbb", true /* isValidWord */);
         assertTrue(binaryDictionary.isValidNgram(beginningOfSentenceContext, "aaa"));
         assertTrue(binaryDictionary.isValidNgram(beginningOfSentenceContext, "bbb"));
         forcePassingLongTime(binaryDictionary);
         assertFalse(binaryDictionary.isValidNgram(beginningOfSentenceContext, "aaa"));
         assertFalse(binaryDictionary.isValidNgram(beginningOfSentenceContext, "bbb"));
-
         onInputWordWithBeginningOfSentenceContext(binaryDictionary, "aaa", true /* isValidWord */);
+        assertFalse(binaryDictionary.isValidNgram(beginningOfSentenceContext, "aaa"));
+        onInputWordWithBeginningOfSentenceContext(binaryDictionary, "aaa", true /* isValidWord */);
+        onInputWordWithBeginningOfSentenceContext(binaryDictionary, "bbb", true /* isValidWord */);
+        assertFalse(binaryDictionary.isValidNgram(beginningOfSentenceContext, "bbb"));
         onInputWordWithBeginningOfSentenceContext(binaryDictionary, "bbb", true /* isValidWord */);
         assertTrue(binaryDictionary.isValidNgram(beginningOfSentenceContext, "aaa"));
         assertTrue(binaryDictionary.isValidNgram(beginningOfSentenceContext, "bbb"));