Merge "Add accessor methods for typed word info to SuggestedWords and for word and source dictionary to SuggestedWordInfo."
diff --git a/common/src/com/android/inputmethod/latin/common/Constants.java b/common/src/com/android/inputmethod/latin/common/Constants.java
index a860d35..29cdb8e 100644
--- a/common/src/com/android/inputmethod/latin/common/Constants.java
+++ b/common/src/com/android/inputmethod/latin/common/Constants.java
@@ -163,7 +163,6 @@
     // TODO: replace the following constants with state in InputTransaction?
     public static final int NOT_A_COORDINATE = -1;
     public static final int SUGGESTION_STRIP_COORDINATE = -2;
-    public static final int SPELL_CHECKER_COORDINATE = -3;
     public static final int EXTERNAL_KEYBOARD_COORDINATE = -4;
 
     // A hint on how many characters to cache from the TextView. A good value of this is given by
@@ -175,10 +174,12 @@
     public static final int MAX_CHARACTERS_FOR_RECAPITALIZATION = 1024 * 100;
 
     // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
+    // TODO: create a overlay and update the value appropriately for the new decoder.
     public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
 
     // (MAX_PREV_WORD_COUNT_FOR_N_GRAM + 1)-gram is supported in Java side. Needs to modify
     // MAX_PREV_WORD_COUNT_FOR_N_GRAM in native/jni/src/defines.h for suggestions.
+    // TODO: create a overlay and update the value appropriately for the new decoder.
     public static final int MAX_PREV_WORD_COUNT_FOR_N_GRAM = 3;
 
     // Key events coming any faster than this are long-presses.
@@ -214,8 +215,6 @@
     public static final int CODE_DASH = '-';
     public static final int CODE_SINGLE_QUOTE = '\'';
     public static final int CODE_DOUBLE_QUOTE = '"';
-    public static final int CODE_QUESTION_MARK = '?';
-    public static final int CODE_EXCLAMATION_MARK = '!';
     public static final int CODE_SLASH = '/';
     public static final int CODE_BACKSLASH = '\\';
     public static final int CODE_VERTICAL_BAR = '|';
@@ -330,6 +329,10 @@
      */
     public static final int DEFAULT_GESTURE_POINTS_CAPACITY = 128;
 
+    public static final int MAX_IME_DECODER_RESULTS = 20;
+    public static final int DECODER_SCORE_SCALAR = 1000000;
+    public static final int DECODER_MAX_SCORE = 1000000000;
+
     private Constants() {
         // This utility class is not publicly instantiable.
     }
diff --git a/common/src/com/android/inputmethod/latin/common/UnicodeSurrogate.java b/common/src/com/android/inputmethod/latin/common/UnicodeSurrogate.java
new file mode 100644
index 0000000..1097463
--- /dev/null
+++ b/common/src/com/android/inputmethod/latin/common/UnicodeSurrogate.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.inputmethod.latin.common;
+
+/**
+ * Emojis are supplementary characters expressed as a low+high pair. For instance,
+ * the emoji U+1F625 is encoded as "\uD83D\uDE25" in UTF-16, where '\uD83D' is in
+ * the range of [0xd800, 0xdbff] and '\uDE25' is in the range of [0xdc00, 0xdfff].
+ * {@see http://docs.oracle.com/javase/6/docs/api/java/lang/Character.html#unicode}
+ */
+public final class UnicodeSurrogate {
+    private static final char LOW_SURROGATE_MIN = '\uD800';
+    private static final char LOW_SURROGATE_MAX = '\uDBFF';
+    private static final char HIGH_SURROGATE_MIN = '\uDC00';
+    private static final char HIGH_SURROGATE_MAX = '\uDFFF';
+
+    public static boolean isLowSurrogate(final char c) {
+        return c >= LOW_SURROGATE_MIN && c <= LOW_SURROGATE_MAX;
+    }
+
+    public static boolean isHighSurrogate(final char c) {
+        return c >= HIGH_SURROGATE_MIN && c <= HIGH_SURROGATE_MAX;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
index 9ab4652..f4c4f1a 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Color;
 import android.preference.PreferenceManager;
 import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
@@ -73,6 +74,7 @@
     private final int mCategoryPageIndicatorBackground;
     private EmojiPalettesAdapter mEmojiPalettesAdapter;
     private final EmojiLayoutParams mEmojiLayoutParams;
+    private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
 
     private ImageButton mDeleteKey;
     private TextView mAlphabetKeyLeft;
@@ -127,6 +129,7 @@
         mCategoryPageIndicatorBackground = emojiPalettesViewAttr.getColor(
                 R.styleable.EmojiPalettesView_categoryPageIndicatorBackground, 0);
         emojiPalettesViewAttr.recycle();
+        mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener();
     }
 
     @Override
@@ -197,7 +200,7 @@
         mDeleteKey = (ImageButton)findViewById(R.id.emoji_keyboard_delete);
         mDeleteKey.setBackgroundResource(mFunctionalKeyBackgroundId);
         mDeleteKey.setTag(Constants.CODE_DELETE);
-        mDeleteKey.setOnTouchListener(this);
+        mDeleteKey.setOnTouchListener(mDeleteKeyOnTouchListener);
 
         // {@link #mAlphabetKeyLeft}, {@link #mAlphabetKeyRight, and spaceKey depend on
         // {@link View.OnClickListener} as well as {@link View.OnTouchListener}.
@@ -366,7 +369,8 @@
     }
 
     public void startEmojiPalettes(final String switchToAlphaLabel,
-                                   final KeyVisualAttributes keyVisualAttr, final KeyboardIconsSet iconSet) {
+                                   final KeyVisualAttributes keyVisualAttr,
+                                   final KeyboardIconsSet iconSet) {
         final int deleteIconResId = iconSet.getIconResourceId(KeyboardIconsSet.NAME_DELETE_KEY);
         if (deleteIconResId != 0) {
             mDeleteKey.setImageResource(deleteIconResId);
@@ -392,6 +396,7 @@
 
     public void setKeyboardActionListener(final KeyboardActionListener listener) {
         mKeyboardActionListener = listener;
+        mDeleteKeyOnTouchListener.setKeyboardActionListener(listener);
     }
 
     private void updateEmojiCategoryPageIdView() {
@@ -427,4 +432,52 @@
             mTabHost.setCurrentTab(newTabId);
         }
     }
+
+    private static class DeleteKeyOnTouchListener implements OnTouchListener {
+        private KeyboardActionListener mKeyboardActionListener =
+                KeyboardActionListener.EMPTY_LISTENER;
+
+        public void setKeyboardActionListener(final KeyboardActionListener listener) {
+            mKeyboardActionListener = listener;
+        }
+
+        @Override
+        public boolean onTouch(final View v, final MotionEvent event) {
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    onTouchDown(v);
+                    return true;
+                case MotionEvent.ACTION_MOVE:
+                    final float x = event.getX();
+                    final float y = event.getY();
+                    if (x < 0.0f || v.getWidth() < x || y < 0.0f || v.getHeight() < y) {
+                        // Stop generating key events once the finger moves away from the view area.
+                        onTouchCanceled(v);
+                    }
+                    return true;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    onTouchUp(v);
+                    return true;
+            }
+            return false;
+        }
+
+        private void onTouchDown(final View v) {
+            mKeyboardActionListener.onPressKey(Constants.CODE_DELETE,
+                    0 /* repeatCount */, true /* isSinglePointer */);
+            v.setPressed(true /* pressed */);
+        }
+
+        private void onTouchUp(final View v) {
+            mKeyboardActionListener.onCodeInput(Constants.CODE_DELETE,
+                    NOT_A_COORDINATE, NOT_A_COORDINATE, false /* isKeyRepeat */);
+            mKeyboardActionListener.onReleaseKey(Constants.CODE_DELETE, false /* withSliding */);
+            v.setPressed(false /* pressed */);
+        }
+
+        private void onTouchCanceled(final View v) {
+            v.setBackgroundColor(Color.TRANSPARENT);
+        }
+    }
 }
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 7d7ed77..16dcb32 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -49,8 +49,7 @@
 
     // Spawned by resuming suggestions. Comes from a span that was in the TextView.
     public static final String TYPE_RESUMED = "resumed";
-    public static final PhonyDictionary DICTIONARY_RESUMED =
-            new PhonyDictionary(TYPE_RESUMED);
+    public static final PhonyDictionary DICTIONARY_RESUMED = new PhonyDictionary(TYPE_RESUMED);
 
     // The following types of dictionary have actual functional instances. We don't need final
     // phony dictionary instances for them.
@@ -60,10 +59,6 @@
     public static final String TYPE_USER = "user";
     // User history dictionary internal to LatinIME.
     public static final String TYPE_USER_HISTORY = "history";
-    // Personalization dictionary.
-    public static final String TYPE_PERSONALIZATION = "personalization";
-    // Contextual dictionary.
-    public static final String TYPE_CONTEXTUAL = "contextual";
     public final String mDictType;
     // The locale for this dictionary. May be null if unknown (phony dictionary for example).
     public final Locale mLocale;
@@ -76,9 +71,7 @@
             TYPE_USER_TYPED,
             TYPE_USER,
             TYPE_CONTACTS,
-            TYPE_USER_HISTORY,
-            TYPE_PERSONALIZATION,
-            TYPE_CONTEXTUAL));
+            TYPE_USER_HISTORY));
 
     public Dictionary(final String dictType, final Locale locale) {
         mDictType = dictType;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 401b528..d174b40 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -21,6 +21,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.keyboard.KeyboardLayout;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
 import com.android.inputmethod.latin.utils.SuggestionResults;
 
@@ -42,6 +43,35 @@
  * DictionaryFacilitator as a client for interacting with dictionaries.
  */
 public interface DictionaryFacilitator {
+
+    public static final String[] ALL_DICTIONARY_TYPES = new String[] {
+            Dictionary.TYPE_MAIN,
+            Dictionary.TYPE_USER_HISTORY,
+            Dictionary.TYPE_USER,
+            Dictionary.TYPE_CONTACTS};
+
+    public static final String[] DYNAMIC_DICTIONARY_TYPES = new String[] {
+            Dictionary.TYPE_USER_HISTORY,
+            Dictionary.TYPE_USER,
+            Dictionary.TYPE_CONTACTS};
+
+    /**
+     * {@link Dictionary#TYPE_USER} is deprecated, except for the spelling service.
+     */
+    public static final String[] DICTIONARY_TYPES_FOR_SPELLING = new String[] {
+            Dictionary.TYPE_MAIN,
+            Dictionary.TYPE_USER_HISTORY,
+            Dictionary.TYPE_USER,
+            Dictionary.TYPE_CONTACTS};
+
+    /**
+     * {@link Dictionary#TYPE_USER} is deprecated, except for the spelling service.
+     */
+    public static final String[] DICTIONARY_TYPES_FOR_SUGGESTIONS = new String[] {
+            Dictionary.TYPE_MAIN,
+            Dictionary.TYPE_USER_HISTORY,
+            Dictionary.TYPE_CONTACTS};
+
     /**
      * Returns whether this facilitator is exactly for this list of locales.
      *
@@ -115,8 +145,6 @@
 
     boolean hasAtLeastOneUninitializedMainDictionary();
 
-    boolean hasPersonalizationDictionary();
-
     void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
             throws InterruptedException;
 
@@ -133,9 +161,12 @@
     // TODO: Revise the way to fusion suggestion results.
     SuggestionResults getSuggestionResults(final WordComposer composer,
             final NgramContext ngramContext, final long proximityInfoHandle,
-            final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId);
+            final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId,
+            final int inputStyle, final KeyboardLayout keyboardLayout);
 
-    boolean isValidWord(final String word, final boolean ignoreCase);
+    boolean isValidSpellingWord(final String word);
+
+    boolean isValidSuggestionWord(final String word);
 
     int getFrequency(final String word);
 
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
index 3c390db..96603ef 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorImpl.java
@@ -23,6 +23,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.keyboard.KeyboardLayout;
 import com.android.inputmethod.latin.NgramContext.WordInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
@@ -38,7 +39,6 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -78,14 +78,6 @@
     private final Object mLock = new Object();
     private final DistracterFilter mDistracterFilter;
 
-    private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
-            new String[] {
-                Dictionary.TYPE_MAIN,
-                Dictionary.TYPE_USER_HISTORY,
-                Dictionary.TYPE_USER,
-                Dictionary.TYPE_CONTACTS,
-            };
-
     public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
             DICT_TYPE_TO_CLASS = new HashMap<>();
 
@@ -99,10 +91,6 @@
     private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
             new Class[] { Context.class, Locale.class, File.class, String.class, String.class };
 
-    private static final String[] SUB_DICT_TYPES =
-            Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */,
-                    DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
-
     /**
      * Returns whether this facilitator is exactly for this list of locales.
      *
@@ -392,8 +380,6 @@
         }
         if (usePersonalizedDicts) {
             subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
-            subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
-            subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
         }
 
         // Gather all dictionaries. We'll remove them from the list to clean up later.
@@ -405,7 +391,7 @@
             if (null == currentDictionaryGroupForLocale) {
                 continue;
             }
-            for (final String dictType : SUB_DICT_TYPES) {
+            for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
                 if (currentDictionaryGroupForLocale.hasDict(dictType, account)) {
                     dictTypeForLocale.add(dictType);
                 }
@@ -564,7 +550,7 @@
             mDictionaryGroups = new DictionaryGroup[] { mMostProbableDictionaryGroup };
         }
         for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
-            for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+            for (final String dictType : ALL_DICTIONARY_TYPES) {
                 dictionaryGroup.closeDict(dictType);
             }
         }
@@ -600,16 +586,6 @@
         return false;
     }
 
-    public boolean hasPersonalizationDictionary() {
-        final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
-        for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
-            if (dictionaryGroup.hasDict(Dictionary.TYPE_PERSONALIZATION, null /* account */)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public void waitForLoadingMainDictionaries(final long timeout, final TimeUnit unit)
             throws InterruptedException {
         mLatchForWaitingLoadingMainDictionaries.await(timeout, unit);
@@ -659,8 +635,7 @@
         final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
         final String secondWord;
         if (wasAutoCapitalized) {
-            if (isValidWord(word, false /* ignoreCase */)
-                    && !isValidWord(lowerCasedWord, false /* ignoreCase */)) {
+            if (isValidSuggestionWord(word) && !isValidSuggestionWord(lowerCasedWord)) {
                 // If the word was auto-capitalized and exists only as a capitalized word in the
                 // dictionary, then we must not downcase it before registering it. For example,
                 // the name of the contacts in start-of-sentence position would come here with the
@@ -708,21 +683,21 @@
 
     public void removeWordFromPersonalizedDicts(final String word) {
         removeWord(Dictionary.TYPE_USER_HISTORY, word);
-        removeWord(Dictionary.TYPE_PERSONALIZATION, word);
-        removeWord(Dictionary.TYPE_CONTEXTUAL, word);
     }
 
     // TODO: Revise the way to fusion suggestion results.
-    public SuggestionResults getSuggestionResults(final WordComposer composer,
-            final NgramContext ngramContext, final long proximityInfoHandle,
-            final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) {
+    @Override
+    public SuggestionResults getSuggestionResults(WordComposer composer,
+            NgramContext ngramContext, long proximityInfoHandle,
+            SettingsValuesForSuggestion settingsValuesForSuggestion, int sessionId,
+            int inputStyle, KeyboardLayout keyboardLayout) {
         final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
         final SuggestionResults suggestionResults = new SuggestionResults(
                 SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext());
         final float[] weightOfLangModelVsSpatialModel =
                 new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
         for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
-            for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+            for (final String dictType : DICTIONARY_TYPES_FOR_SUGGESTIONS) {
                 final Dictionary dictionary = dictionaryGroup.getDict(dictType);
                 if (null == dictionary) continue;
                 final float weightForLocale = composer.isBatchMode()
@@ -742,7 +717,15 @@
         return suggestionResults;
     }
 
-    public boolean isValidWord(final String word, final boolean ignoreCase) {
+    public boolean isValidSpellingWord(final String word) {
+        return isValidWord(word, DICTIONARY_TYPES_FOR_SPELLING);
+    }
+
+    public boolean isValidSuggestionWord(final String word) {
+        return isValidWord(word, DICTIONARY_TYPES_FOR_SUGGESTIONS);
+    }
+
+    private boolean isValidWord(final String word, final String[] dictionariesToCheck) {
         if (TextUtils.isEmpty(word)) {
             return false;
         }
@@ -751,15 +734,13 @@
             if (dictionaryGroup.mLocale == null) {
                 continue;
             }
-            final String lowerCasedWord = word.toLowerCase(dictionaryGroup.mLocale);
-            for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+            for (final String dictType : dictionariesToCheck) {
                 final Dictionary dictionary = dictionaryGroup.getDict(dictType);
                 // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
                 // would be immutable once it's finished initializing, but concretely a null test is
                 // probably good enough for the time being.
                 if (null == dictionary) continue;
-                if (dictionary.isValidWord(word)
-                        || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+                if (dictionary.isValidWord(word)) {
                     return true;
                 }
             }
@@ -775,7 +756,7 @@
         int maxFreq = Dictionary.NOT_A_PROBABILITY;
         final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
         for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
-            for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+            for (final String dictType : ALL_DICTIONARY_TYPES) {
                 final Dictionary dictionary = dictionaryGroup.getDict(dictType);
                 if (dictionary == null) continue;
                 final int tempFreq;
@@ -814,10 +795,6 @@
         clearSubDictionary(Dictionary.TYPE_USER_HISTORY);
     }
 
-    public void clearContextualDictionary() {
-        clearSubDictionary(Dictionary.TYPE_CONTEXTUAL);
-    }
-
     public void dumpDictionaryForDebug(final String dictName) {
         final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
         for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
@@ -835,7 +812,7 @@
         final ArrayList<Pair<String, DictionaryStats>> statsOfEnabledSubDicts = new ArrayList<>();
         final DictionaryGroup[] dictionaryGroups = mDictionaryGroups;
         for (final DictionaryGroup dictionaryGroup : dictionaryGroups) {
-            for (final String dictType : SUB_DICT_TYPES) {
+            for (final String dictType : DYNAMIC_DICTIONARY_TYPES) {
                 final ExpandableBinaryDictionary dictionary = dictionaryGroup.getSubDict(dictType);
                 if (dictionary == null) continue;
                 statsOfEnabledSubDicts.add(new Pair<>(dictType, dictionary.getDictionaryStats()));
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
index 13bd151..666813d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorLruCache.java
@@ -128,7 +128,7 @@
     public DictionaryFacilitator get(final Locale locale) {
         DictionaryFacilitator dictionaryFacilitator = mLruCache.get(locale);
         if (dictionaryFacilitator != null) {
-            // dictionary falicitator for the locale is in the cache.
+            // dictionary facilitator for the locale is in the cache.
             return dictionaryFacilitator;
         }
         synchronized (mLock) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 8227008..9a1df49 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1520,7 +1520,8 @@
             return;
         }
         mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard.getProximityInfo(),
-                mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback);
+                mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback,
+                keyboard.getKeyboardLayout());
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/NgramContext.java b/java/src/com/android/inputmethod/latin/NgramContext.java
index b477312..2d66fb0 100644
--- a/java/src/com/android/inputmethod/latin/NgramContext.java
+++ b/java/src/com/android/inputmethod/latin/NgramContext.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.latin.common.Constants;
 import com.android.inputmethod.latin.common.StringUtils;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 import javax.annotation.Nonnull;
@@ -38,6 +39,10 @@
     public static final NgramContext BEGINNING_OF_SENTENCE =
             new NgramContext(WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO);
 
+    public static final String BEGINNING_OF_SENTENCE_TAG = "<S>";
+
+    public static final String CONTEXT_SEPARATOR = " ";
+
     /**
      * Word information used to represent previous words information.
      */
@@ -114,6 +119,31 @@
         return new NgramContext(prevWordsInfo);
     }
 
+
+    /**
+     * Extracts the previous words context.
+     *
+     * @return a String with the previous words separated by white space.
+     */
+    public String extractPrevWordsContext() {
+        final ArrayList<String> terms = new ArrayList<>();
+        for (int i = mPrevWordsInfo.length - 1; i >= 0; --i) {
+            if (mPrevWordsInfo[i] != null && mPrevWordsInfo[i].isValid()) {
+                final NgramContext.WordInfo wordInfo = mPrevWordsInfo[i];
+                if (wordInfo.mIsBeginningOfSentence) {
+                    terms.add(BEGINNING_OF_SENTENCE_TAG);
+                } else {
+                    final String term = wordInfo.mWord.toString();
+                    if (!term.isEmpty()) {
+                        terms.add(term);
+                    }
+                }
+            }
+        }
+        return terms.size() == 0 ? BEGINNING_OF_SENTENCE_TAG
+                : TextUtils.join(CONTEXT_SEPARATOR, terms);
+    }
+
     public boolean isValid() {
         return mPrevWordsCount > 0 && mPrevWordsInfo[0].isValid();
     }
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 834f747..660a051 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -16,14 +16,12 @@
 
 package com.android.inputmethod.latin;
 
-import android.graphics.Color;
 import android.inputmethodservice.InputMethodService;
 import android.os.Build;
 import android.os.Bundle;
 import android.text.SpannableStringBuilder;
-import android.text.Spanned;
 import android.text.TextUtils;
-import android.text.style.BackgroundColorSpan;
+import android.text.style.CharacterStyle;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -35,6 +33,7 @@
 
 import com.android.inputmethod.compat.InputConnectionCompatUtils;
 import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.UnicodeSurrogate;
 import com.android.inputmethod.latin.common.StringUtils;
 import com.android.inputmethod.latin.inputlogic.PrivateCommandPerformer;
 import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
@@ -91,16 +90,10 @@
     private final StringBuilder mComposingText = new StringBuilder();
 
     /**
-     * This variable is a temporary object used in
-     * {@link #commitTextWithBackgroundColor(CharSequence,int,int,int)} to avoid object creation.
+     * This variable is a temporary object used in {@link #commitText(CharSequence,int)}
+     * to avoid object creation.
      */
     private SpannableStringBuilder mTempObjectForCommitText = new SpannableStringBuilder();
-    /**
-     * This variable is used to track whether the last committed text had the background color or
-     * not.
-     * TODO: Omit this flag if possible.
-     */
-    private boolean mLastCommittedTextHasBackgroundColor = false;
 
     private final InputMethodService mParent;
     InputConnection mIC;
@@ -239,39 +232,18 @@
         // it works, but it's wrong and should be fixed.
         mCommittedTextBeforeComposingText.append(mComposingText);
         mComposingText.setLength(0);
-        // TODO: Clear this flag in setComposingRegion() and setComposingText() as well if needed.
-        mLastCommittedTextHasBackgroundColor = false;
         if (null != mIC) {
             mIC.finishComposingText();
         }
     }
 
     /**
-     * Synonym of {@code commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT}.
+     * Calls {@link InputConnection#commitText(CharSequence, int)}.
+     *
      * @param text The text to commit. This may include styles.
-     * See {@link InputConnection#commitText(CharSequence, int)}.
      * @param newCursorPosition The new cursor position around the text.
-     * See {@link InputConnection#commitText(CharSequence, int)}.
      */
     public void commitText(final CharSequence text, final int newCursorPosition) {
-        commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT, text.length());
-    }
-
-    /**
-     * Calls {@link InputConnection#commitText(CharSequence, int)} with the given background color.
-     * @param text The text to commit. This may include styles.
-     * See {@link InputConnection#commitText(CharSequence, int)}.
-     * @param newCursorPosition The new cursor position around the text.
-     * See {@link InputConnection#commitText(CharSequence, int)}.
-     * @param color The background color to be attached. Set {@link Color#TRANSPARENT} to disable
-     * the background color. Note that this method specifies {@link BackgroundColorSpan} with
-     * {@link Spanned#SPAN_COMPOSING} flag, meaning that the background color persists until
-     * {@link #finishComposingText()} is called.
-     * @param coloredTextLength the length of text, in Java chars, which should be rendered with
-     * the given background color.
-     */
-    public void commitTextWithBackgroundColor(final CharSequence text, final int newCursorPosition,
-            final int color, final int coloredTextLength) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(text);
@@ -281,44 +253,32 @@
         mExpectedSelStart += text.length() - mComposingText.length();
         mExpectedSelEnd = mExpectedSelStart;
         mComposingText.setLength(0);
-        mLastCommittedTextHasBackgroundColor = false;
         if (null != mIC) {
-            if (color == Color.TRANSPARENT) {
-                mIC.commitText(text, newCursorPosition);
-            } else {
-                mTempObjectForCommitText.clear();
-                mTempObjectForCommitText.append(text);
-                final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(color);
-                final int spanLength = Math.min(coloredTextLength, text.length());
-                mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, spanLength,
-                        Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                mIC.commitText(mTempObjectForCommitText, newCursorPosition);
-                mLastCommittedTextHasBackgroundColor = true;
+            mTempObjectForCommitText.clear();
+            mTempObjectForCommitText.append(text);
+            final CharacterStyle[] spans = mTempObjectForCommitText.getSpans(
+                    0, text.length(), CharacterStyle.class);
+            for (final CharacterStyle span : spans) {
+                final int spanStart = mTempObjectForCommitText.getSpanStart(span);
+                final int spanEnd = mTempObjectForCommitText.getSpanEnd(span);
+                final int spanFlags = mTempObjectForCommitText.getSpanFlags(span);
+                // We have to adjust the end of the span to include an additional character.
+                // This is to avoid splitting a unicode surrogate pair.
+                // See com.android.inputmethod.latin.common.Constants.UnicodeSurrogate
+                // See https://b.corp.google.com/issues/19255233
+                if (0 < spanEnd && spanEnd < mTempObjectForCommitText.length()) {
+                    final char spanEndChar = mTempObjectForCommitText.charAt(spanEnd - 1);
+                    final char nextChar = mTempObjectForCommitText.charAt(spanEnd);
+                    if (UnicodeSurrogate.isLowSurrogate(spanEndChar)
+                            && UnicodeSurrogate.isHighSurrogate(nextChar)) {
+                        mTempObjectForCommitText.setSpan(span, spanStart, spanEnd + 1, spanFlags);
+                    }
+                }
             }
+            mIC.commitText(mTempObjectForCommitText, newCursorPosition);
         }
     }
 
-    /**
-     * Removes the background color from the highlighted text if necessary. Should be called while
-     * there is no on-going composing text.
-     *
-     * <p>CAVEAT: This method internally calls {@link InputConnection#finishComposingText()}.
-     * Be careful of any unexpected side effects.</p>
-     */
-    public void removeBackgroundColorFromHighlightedTextIfNecessary() {
-        // TODO: We haven't yet full tested if we really need to check this flag or not. Omit this
-        // flag if everything works fine without this condition.
-        if (!mLastCommittedTextHasBackgroundColor) {
-            return;
-        }
-        if (mComposingText.length() > 0) {
-            Log.e(TAG, "clearSpansWithComposingFlags should be called when composing text is " +
-                    "empty. mComposingText=" + mComposingText);
-            return;
-        }
-        finishComposingText();
-    }
-
     public CharSequence getSelectedText(final int flags) {
         return (null == mIC) ? null : mIC.getSelectedText(flags);
     }
@@ -946,8 +906,6 @@
         }
     }
 
-    private boolean mCursorAnchorInfoMonitorEnabled = false;
-
     /**
      * Requests the editor to call back {@link InputMethodManager#updateCursorAnchorInfo}.
      * @param enableMonitor {@code true} to request the editor to call back the method whenever the
@@ -962,23 +920,10 @@
     public boolean requestCursorUpdates(final boolean enableMonitor,
             final boolean requestImmediateCallback) {
         mIC = mParent.getCurrentInputConnection();
-        final boolean scheduled;
-        if (null != mIC) {
-            scheduled = InputConnectionCompatUtils.requestCursorUpdates(mIC, enableMonitor,
-                    requestImmediateCallback);
-        } else {
-            scheduled = false;
+        if (mIC == null) {
+            return false;
         }
-        mCursorAnchorInfoMonitorEnabled = (scheduled && enableMonitor);
-        return scheduled;
-    }
-
-    /**
-     * @return {@code true} if the application reported that the monitor mode of
-     * {@link InputMethodService#onUpdateCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfo)}
-     * is currently enabled.
-     */
-    public boolean isCursorAnchorInfoMonitorEnabled() {
-        return mCursorAnchorInfoMonitorEnabled;
+        return InputConnectionCompatUtils.requestCursorUpdates(
+                mIC, enableMonitor, requestImmediateCallback);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0bf0f68..ddb2b53 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -18,6 +18,7 @@
 
 import android.text.TextUtils;
 
+import com.android.inputmethod.keyboard.KeyboardLayout;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.common.Constants;
@@ -97,14 +98,16 @@
             final NgramContext ngramContext, final ProximityInfo proximityInfo,
             final SettingsValuesForSuggestion settingsValuesForSuggestion,
             final boolean isCorrectionEnabled, final int inputStyle, final int sequenceNumber,
-            final OnGetSuggestedWordsCallback callback) {
+            final OnGetSuggestedWordsCallback callback,
+            final KeyboardLayout keyboardLayout) {
         if (wordComposer.isBatchMode()) {
             getSuggestedWordsForBatchInput(wordComposer, ngramContext, proximityInfo,
-                    settingsValuesForSuggestion, inputStyle, sequenceNumber, callback);
+                    settingsValuesForSuggestion, inputStyle, sequenceNumber, callback,
+                    keyboardLayout);
         } else {
             getSuggestedWordsForNonBatchInput(wordComposer, ngramContext, proximityInfo,
                     settingsValuesForSuggestion, inputStyle, isCorrectionEnabled,
-                    sequenceNumber, callback);
+                    sequenceNumber, callback, keyboardLayout);
         }
     }
 
@@ -163,7 +166,8 @@
             final NgramContext ngramContext, final ProximityInfo proximityInfo,
             final SettingsValuesForSuggestion settingsValuesForSuggestion,
             final int inputStyleIfNotPrediction, final boolean isCorrectionEnabled,
-            final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
+            final int sequenceNumber, final OnGetSuggestedWordsCallback callback,
+            final KeyboardLayout keyboardLayout) {
         final String typedWordString = wordComposer.getTypedWord();
         final int trailingSingleQuotesCount =
                 StringUtils.getTrailingSingleQuotesCount(typedWordString);
@@ -173,7 +177,8 @@
 
         final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
                 wordComposer, ngramContext, proximityInfo.getNativeProximityInfo(),
-                settingsValuesForSuggestion, SESSION_ID_TYPING);
+                settingsValuesForSuggestion, SESSION_ID_TYPING, inputStyleIfNotPrediction,
+                keyboardLayout);
         final Locale mostProbableLocale = mDictionaryFacilitator.getMostProbableLocale();
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
                 getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
@@ -270,7 +275,9 @@
             hasAutoCorrection = false;
         } else {
             final SuggestedWordInfo firstSuggestion = suggestionResults.first();
-            if (!AutoCorrectionUtils.suggestionExceedsThreshold(
+            if (suggestionResults.mAutocorrectRecommendation) {
+                hasAutoCorrection = true;
+            } else if (!AutoCorrectionUtils.suggestionExceedsThreshold(
                     firstSuggestion, consideredWord, mAutoCorrectionThreshold)) {
                 // Score is too low for autocorrect
                 hasAutoCorrection = false;
@@ -339,10 +346,11 @@
             final NgramContext ngramContext, final ProximityInfo proximityInfo,
             final SettingsValuesForSuggestion settingsValuesForSuggestion,
             final int inputStyle, final int sequenceNumber,
-            final OnGetSuggestedWordsCallback callback) {
+            final OnGetSuggestedWordsCallback callback,
+            final KeyboardLayout keyboardLayout) {
         final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
                 wordComposer, ngramContext, proximityInfo.getNativeProximityInfo(),
-                settingsValuesForSuggestion, SESSION_ID_GESTURE);
+                settingsValuesForSuggestion, SESSION_ID_GESTURE, inputStyle, keyboardLayout);
         // For transforming words that don't come from a dictionary, because it's our best bet
         final Locale defaultLocale = mDictionaryFacilitator.getMostProbableLocale();
         final ArrayList<SuggestedWordInfo> suggestionsContainer =
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index e80e362..e6f2f1e 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.event.CombinerChain;
 import com.android.inputmethod.event.Event;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -464,4 +465,14 @@
     public String getRejectedBatchModeSuggestion() {
         return mRejectedBatchModeSuggestion;
     }
+
+    @UsedForTesting
+    void addInputPointerForTest(int index, int keyX, int keyY) {
+        mInputPointers.addPointerAt(index, keyX, keyY, 0, 0);
+    }
+
+    @UsedForTesting
+    void setTypedWordCacheForTests(String typedWordCacheForTests) {
+        mTypedWordCache = typedWordCacheForTests;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 18927ce..9154cc3 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -32,6 +32,7 @@
 import com.android.inputmethod.compat.SuggestionSpanUtils;
 import com.android.inputmethod.event.Event;
 import com.android.inputmethod.event.InputTransaction;
+import com.android.inputmethod.keyboard.KeyboardLayout;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.Dictionary;
@@ -474,9 +475,7 @@
         handler.cancelUpdateSuggestionStrip();
         ++mAutoCommitSequenceNumber;
         mConnection.beginBatchEdit();
-        if (!mWordComposer.isComposingWord()) {
-            mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
-        } else {
+        if (mWordComposer.isComposingWord()) {
             if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
                 // If we are in the middle of a recorrection, we need to commit the recorrection
                 // first so that we can insert the batch input at the current cursor position.
@@ -767,10 +766,6 @@
             final InputTransaction inputTransaction,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
-        if (!mWordComposer.isComposingWord()) {
-            mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
-        }
-
         final int codePoint = event.mCodePoint;
         mSpaceState = SpaceState.NONE;
         if (inputTransaction.mSettingsValues.isWordSeparator(codePoint)
@@ -2115,7 +2110,8 @@
 
     public void getSuggestedWords(final SettingsValues settingsValues,
             final ProximityInfo proximityInfo, final int keyboardShiftMode, final int inputStyle,
-            final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
+            final int sequenceNumber, final OnGetSuggestedWordsCallback callback,
+            final KeyboardLayout keyboardLayout) {
         mWordComposer.adviseCapitalizedModeBeforeFetchingSuggestions(
                 getActualCapsMode(settingsValues, keyboardShiftMode));
         mSuggest.getSuggestedWords(mWordComposer,
@@ -2129,7 +2125,7 @@
                 new SettingsValuesForSuggestion(settingsValues.mBlockPotentiallyOffensive,
                         settingsValues.mPhraseGestureEnabled),
                 settingsValues.mAutoCorrectionEnabledPerUserSettings,
-                inputStyle, sequenceNumber, callback);
+                inputStyle, sequenceNumber, callback, keyboardLayout);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 8744020..477e570 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -27,6 +27,7 @@
 
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardLayout;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.DictionaryFacilitator;
@@ -34,6 +35,7 @@
 import com.android.inputmethod.latin.NgramContext;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
+import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
 import com.android.inputmethod.latin.utils.ScriptUtils;
@@ -152,14 +154,15 @@
         try {
             DictionaryFacilitator dictionaryFacilitatorForLocale =
                     mDictionaryFacilitatorCache.get(locale);
-            return dictionaryFacilitatorForLocale.isValidWord(word, false /* igroreCase */);
+            return dictionaryFacilitatorForLocale.isValidSpellingWord(word);
         } finally {
             mSemaphore.release();
         }
     }
 
     public SuggestionResults getSuggestionResults(final Locale locale, final WordComposer composer,
-            final NgramContext ngramContext, final ProximityInfo proximityInfo) {
+            final NgramContext ngramContext, final ProximityInfo proximityInfo,
+            final KeyboardLayout keyboardLayout) {
         Integer sessionId = null;
         mSemaphore.acquireUninterruptibly();
         try {
@@ -168,7 +171,7 @@
                     mDictionaryFacilitatorCache.get(locale);
             return dictionaryFacilitatorForLocale.getSuggestionResults(composer, ngramContext,
                     proximityInfo.getNativeProximityInfo(), mSettingsValuesForSuggestion,
-                    sessionId);
+                    sessionId, SuggestedWords.INPUT_STYLE_TYPING, keyboardLayout);
         } finally {
             if (sessionId != null) {
                 mSessionIdPool.add(sessionId);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 832bfd0..0b5e12f 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -29,6 +29,7 @@
 
 import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardLayout;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.NgramContext;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -271,18 +272,21 @@
             final int[] codePoints = StringUtils.toCodePointArray(text);
             final int[] coordinates;
             final ProximityInfo proximityInfo;
+            final KeyboardLayout keyboardLayout;
             if (null == keyboard) {
                 coordinates = CoordinateUtils.newCoordinateArray(codePoints.length,
                         Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
                 proximityInfo = null;
+                keyboardLayout = null;
             } else {
                 coordinates = keyboard.getCoordinates(codePoints);
                 proximityInfo = keyboard.getProximityInfo();
+                keyboardLayout = keyboard.getKeyboardLayout();
             }
             composer.setComposingWord(codePoints, coordinates);
             // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
             final SuggestionResults suggestionResults = mService.getSuggestionResults(
-                    mLocale, composer, ngramContext, proximityInfo);
+                    mLocale, composer, ngramContext, proximityInfo, keyboardLayout);
             final Result result = getResult(capitalizeType, mLocale, suggestionsLimit,
                     mService.getRecommendedThreshold(), text, suggestionResults);
             isInDict = isInDictForAnyCapitalization(text, capitalizeType);
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 56a04a8..09bb03f 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -38,6 +38,7 @@
 import com.android.inputmethod.latin.DictionaryFacilitatorLruCache;
 import com.android.inputmethod.latin.NgramContext;
 import com.android.inputmethod.latin.RichInputMethodSubtype;
+import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.common.StringUtils;
@@ -195,8 +196,7 @@
             mDistractersCache.put(cacheKey, Boolean.TRUE);
             return true;
         }
-        final boolean Word = dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */);
-        if (Word) {
+        if (dictionaryFacilitator.isValidSuggestionWord(testedWord)) {
             // Valid word is not a distracter.
             if (DEBUG) {
                 Log.d(TAG, "isDistracter: false (valid word)");
@@ -252,7 +252,9 @@
             suggestionResults = dictionaryFacilitator.getSuggestionResults(composer,
                     NgramContext.EMPTY_PREV_WORDS_INFO,
                     keyboard.getProximityInfo().getNativeProximityInfo(),
-                    settingsValuesForSuggestion, 0 /* sessionId */);
+                    settingsValuesForSuggestion, 0 /* sessionId */,
+                    SuggestedWords.INPUT_STYLE_TYPING,
+                    keyboard.getKeyboardLayout());
         }
         if (suggestionResults.isEmpty()) {
             return false;
@@ -288,14 +290,14 @@
             final Locale locale) {
         final DictionaryFacilitator dictionaryFacilitator =
                 mDictionaryFacilitatorLruCache.get(locale);
-        if (dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */)) {
+        if (dictionaryFacilitator.isValidSuggestionWord(testedWord)) {
             return false;
         }
-        final String lowerCaseTargetWord = testedWord.toLowerCase(locale);
-        if (testedWord.equals(lowerCaseTargetWord)) {
+        final String lowerCaseWord = testedWord.toLowerCase(locale);
+        if (testedWord.equals(lowerCaseWord)) {
             return false;
         }
-        if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
+        if (dictionaryFacilitator.isValidSuggestionWord(lowerCaseWord)) {
             return true;
         }
         if (StringUtils.getCapitalizationType(testedWord) == StringUtils.CAPITALIZE_FIRST
@@ -314,10 +316,10 @@
             return HandlingType.getHandlingType(false /* shouldBeLowerCased */, false /* isOov */);
         }
         final boolean shouldBeLowerCased = shouldBeLowerCased(ngramContext, testedWord, locale);
-        final String caseModifiedWord =
-                shouldBeLowerCased ? testedWord.toLowerCase(locale) : testedWord;
-        final boolean isOov = !mDictionaryFacilitatorLruCache.get(locale).isValidWord(
-                caseModifiedWord, false /* ignoreCase */);
+        final String caseModifiedWord = shouldBeLowerCased
+                ? testedWord.toLowerCase(locale) : testedWord;
+        final boolean isOov = !mDictionaryFacilitatorLruCache.get(locale).isValidSuggestionWord(
+                caseModifiedWord);
         return HandlingType.getHandlingType(shouldBeLowerCased, isOov);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
index b319aeb..10e3994 100644
--- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
+++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
@@ -33,14 +33,21 @@
     // TODO: Instead of a boolean , we may want to include the context of this suggestion results,
     // such as {@link NgramContext}.
     public final boolean mIsBeginningOfSentence;
+    public final boolean mAutocorrectRecommendation;
     private final int mCapacity;
 
     public SuggestionResults(final int capacity, final boolean isBeginningOfSentence) {
-        this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence);
+        this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence, false);
     }
 
-    private SuggestionResults(final Comparator<SuggestedWordInfo> comparator,
-            final int capacity, final boolean isBeginningOfSentence) {
+    public SuggestionResults(final int capacity, final boolean isBeginningOfSentence,
+            final boolean autocorrectRecommendation) {
+        this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence,
+                autocorrectRecommendation);
+    }
+
+    private SuggestionResults(final Comparator<SuggestedWordInfo> comparator, final int capacity,
+            final boolean isBeginningOfSentence, final boolean autocorrectRecommendation) {
         super(comparator);
         mCapacity = capacity;
         if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) {
@@ -49,6 +56,7 @@
             mRawSuggestions = null;
         }
         mIsBeginningOfSentence = isBeginningOfSentence;
+        mAutocorrectRecommendation = autocorrectRecommendation;
     }
 
     @Override
diff --git a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutTest.java b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutTest.java
index 9aced5c..733bf96 100644
--- a/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutTest.java
+++ b/tests/src/com/android/inputmethod/keyboard/KeyboardLayoutTest.java
@@ -45,8 +45,10 @@
         assertEquals(0, keyboardLayout.getKeyXCoordinates().length);
         assertEquals(0, keyboardLayout.getKeyYCoordinates().length);
 
-        Key key1 = new Key("label1", 101, 102, "101", "101hint", 103, 104, 105, 106, 1100, 1101, 2, 2);
-        Key key2 = new Key("label2", 201, 202, "201", "201hint", 203, 204, 205, 206, 2100, 2201, 2, 2);
+        Key key1 = new Key("label1", 101, 102, "101", "101hint", 103, 104, 105, 106, 1100, 1101,
+                10, 10);
+        Key key2 = new Key("label2", 201, 103, "201", "201hint", 203, 204, 205, 206, 2100, 2101,
+                10, 10);
 
         ArrayList<Key> sortedKeys = new ArrayList<>(2);
         sortedKeys.add(key1);
@@ -57,5 +59,23 @@
         assertEquals(2, keyboardLayout.getKeyHeights().length);
         assertEquals(2, keyboardLayout.getKeyXCoordinates().length);
         assertEquals(2, keyboardLayout.getKeyYCoordinates().length);
+
+        assertEquals(102, keyboardLayout.getKeyCodes()[0]);
+        // xCo + horizontalGap/2
+        assertEquals(105 + 5, keyboardLayout.getKeyXCoordinates()[0]);
+        assertEquals(106, keyboardLayout.getKeyYCoordinates()[0]);
+        // width - horizontalGap
+        assertEquals(1100 - 10, keyboardLayout.getKeyWidths()[0]);
+        // height - verticalGap
+        assertEquals(1101 - 10, keyboardLayout.getKeyHeights()[0]);
+
+        assertEquals(103, keyboardLayout.getKeyCodes()[1]);
+        // xCo + horizontalGap/2
+        assertEquals(205 + 5, keyboardLayout.getKeyXCoordinates()[1]);
+        assertEquals(206, keyboardLayout.getKeyYCoordinates()[1]);
+        // width - horizontalGap
+        assertEquals(2100 - 10, keyboardLayout.getKeyWidths()[1]);
+        // height - verticalGap
+        assertEquals(2101 - 10, keyboardLayout.getKeyHeights()[1]);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/common/UnicodeSurrogateTests.java b/tests/src/com/android/inputmethod/latin/common/UnicodeSurrogateTests.java
new file mode 100644
index 0000000..59bb082
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/common/UnicodeSurrogateTests.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.common;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class UnicodeSurrogateTests extends AndroidTestCase {
+
+    public void testIsLowSurrogate() {
+        assertFalse(UnicodeSurrogate.isLowSurrogate('\uD7FF'));
+        assertTrue(UnicodeSurrogate.isLowSurrogate('\uD83D'));
+        assertFalse(UnicodeSurrogate.isLowSurrogate('\uDC00'));
+    }
+
+    public void testIsHighSurrogate() {
+        assertFalse(UnicodeSurrogate.isHighSurrogate('\uDBFF'));
+        assertTrue(UnicodeSurrogate.isHighSurrogate('\uDE25'));
+        assertFalse(UnicodeSurrogate.isHighSurrogate('\uE000'));
+    }
+}