Merge "Reword the 'word prediction' option"
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index fc2bd55..e19e01f 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -146,7 +146,7 @@
     <string name="enable" msgid="5031294444630523247">"Aktiver"</string>
     <string name="not_now" msgid="6172462888202790482">"Ikke nå"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Inndatastilen finnes allerede: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
-    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Nyttighetsmodus"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Bruksstudiemodus"</string>
     <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Innstillinger for vibrasjonsvarighet ved tastetrykk"</string>
     <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Innstillinger for lydstyrke ved tastetrykk"</string>
 </resources>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 34fdb0f..e46d0eb 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -44,8 +44,8 @@
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Задержка закрытия"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без задержки"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"По умолчанию"</string>
-    <string name="use_contacts_dict" msgid="4435317977804180815">"Подсказки имен контактов"</string>
-    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Подсказки и исправления на основе имен из контактов"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Подсказывать имена"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Подсказывать исправления на основе имен из списка контактов"</string>
     <string name="enable_span_insert" msgid="7204653105667167620">"Автоисправление"</string>
     <string name="enable_span_insert_summary" msgid="2947317657871394467">"Показывать варианты исправления"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Заглавные автоматически"</string>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 9c53f0e..6c66a48 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -296,7 +296,7 @@
     <string name="subtype_locale">Language</string>
     <!-- Title of the spinner for choosing a keyboard layout of custom style in the settings dialog [CHAR LIMIT=15] -->
     <string name="keyboard_layout_set">Layout</string>
-    <!-- The message of the dialog to note that a custom input style needs to be enabled. [CHAR LIMIT=64] -->
+    <!-- The message of the dialog to note that a custom input style needs to be enabled. [CHAR LIMIT=130] -->
     <string name="custom_input_style_note_message">"Your custom input style needs to be enabled before you start using it. Do you want to enable it now?"</string>
     <!-- Title of the button to enable a custom input style entry in the settings dialog [CHAR LIMIT=15] -->
     <string name="enable">Enable</string>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index d8bf784..bf88058 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -81,6 +81,12 @@
         android:title="@string/misc_category"
         android:key="misc_settings">
         <CheckBoxPreference
+            android:key="next_word_prediction"
+            android:title="@string/bigram_prediction"
+            android:summary="@string/bigram_prediction_summary"
+            android:persistent="true"
+            android:defaultValue="true" />
+        <CheckBoxPreference
             android:key="usability_study_mode"
             android:title="@string/prefs_usability_study_mode"
             android:persistent="true"
@@ -114,12 +120,6 @@
                 android:summary="@string/use_contacts_dict_summary"
                 android:persistent="true"
                 android:defaultValue="true" />
-            <CheckBoxPreference
-                android:key="next_word_prediction"
-                android:title="@string/bigram_prediction"
-                android:summary="@string/bigram_prediction_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
             <PreferenceScreen
                 android:key="pref_vibration_duration_settings"
                 android:title="@string/prefs_keypress_vibration_duration_settings"/>
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 383298d..e3739eb 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -127,6 +127,8 @@
         private static final int MSG_TYPING_STATE_EXPIRED = 4;
 
         private final KeyTimerParams mParams;
+        // TODO: Suppress layout changes in key repeat mode
+        // TODO: Remove this variable.
         private boolean mInKeyRepeat;
 
         public KeyTimerHandler(LatinKeyboardView outerInstance, KeyTimerParams params) {
@@ -140,8 +142,11 @@
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_REPEAT_KEY:
-                tracker.onRegisterKey(tracker.getKey());
-                startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
+                final Key currentKey = tracker.getKey();
+                if (currentKey != null && currentKey.mCode == msg.arg1) {
+                    tracker.onRegisterKey(currentKey);
+                    startKeyRepeatTimer(tracker, mParams.mKeyRepeatInterval);
+                }
                 break;
             case MSG_LONGPRESS_KEY:
                 if (tracker != null) {
@@ -158,12 +163,14 @@
         }
 
         private void startKeyRepeatTimer(PointerTracker tracker, long delay) {
-            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, tracker), delay);
+            final Key key = tracker.getKey();
+            if (key == null) return;
+            mInKeyRepeat = true;
+            sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
         }
 
         @Override
         public void startKeyRepeatTimer(PointerTracker tracker) {
-            mInKeyRepeat = true;
             startKeyRepeatTimer(tracker, mParams.mKeyRepeatStartTimeout);
         }
 
@@ -451,8 +458,8 @@
      */
     @Override
     public void setKeyboard(Keyboard keyboard) {
-        // Remove any pending messages, except dismissing preview
-        mKeyTimerHandler.cancelKeyTimers();
+        // Remove any pending messages, except dismissing preview and key repeat.
+        mKeyTimerHandler.cancelLongPressTimer();
         super.setKeyboard(keyboard);
         mKeyDetector.setKeyboard(
                 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 34e428e..8aac8d8 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -148,6 +148,7 @@
     // true if this pointer has been long-pressed and is showing a more keys panel.
     private boolean mIsShowingMoreKeysPanel;
 
+    // TODO: Remove this variable.
     // true if this pointer is repeatable key
     private boolean mIsRepeatableKey;
 
@@ -319,6 +320,13 @@
     private void setKeyDetectorInner(KeyDetector keyDetector) {
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
+        final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
+        if (newKey != mCurrentKey) {
+            if (mDrawingProxy != null) {
+                setReleasedKeyGraphics(mCurrentKey);
+            }
+            mCurrentKey = newKey;
+        }
         final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
         mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
     }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index d0613bd..f44e632 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -20,7 +20,9 @@
 import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
 
@@ -40,6 +42,7 @@
      */
     public static final int MAX_WORD_LENGTH = 48;
     public static final int MAX_WORDS = 18;
+    public static final int MAX_SPACES = 16;
 
     private static final String TAG = "BinaryDictionary";
     private static final int MAX_BIGRAMS = 60;
@@ -51,6 +54,7 @@
     private final int[] mInputCodes = new int[MAX_WORD_LENGTH];
     private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
     private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
+    private final int[] mSpaceIndices = new int[MAX_SPACES];
     private final int[] mScores = new int[MAX_WORDS];
     private final int[] mBigramScores = new int[MAX_BIGRAMS];
 
@@ -65,14 +69,12 @@
      * @param offset the offset of the dictionary data within the file.
      * @param length the length of the binary data.
      * @param useFullEditDistance whether to use the full edit distance in suggestions
+     * @param dicTypeId the dictionary type id of the dictionary
      */
     public BinaryDictionary(final Context context,
             final String filename, final long offset, final long length,
-            final boolean useFullEditDistance, final Locale locale) {
-        // Note: at the moment a binary dictionary is always of the "main" type.
-        // Initializing this here will help transitioning out of the scheme where
-        // the Suggest class knows everything about every single dictionary.
-        mDicTypeId = Suggest.DIC_MAIN;
+            final boolean useFullEditDistance, final Locale locale, final int dicTypeId) {
+        mDicTypeId = dicTypeId;
         mUseFullEditDistance = useFullEditDistance;
         loadDictionary(filename, offset, length);
     }
@@ -87,8 +89,10 @@
     private native int getFrequencyNative(long dict, int[] word, int wordLength);
     private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
     private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
-            int[] yCoordinates, int[] inputCodes, int codesSize, int[] prevWordForBigrams,
-            boolean useFullEditDistance, char[] outputChars, int[] scores);
+            int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize,
+            int commitPoint, boolean isGesture, int dicTypeId,
+            int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
+            int[] scores, int[] outputIndices);
     private native int getBigramsNative(long dict, int[] prevWord, int prevWordLength,
             int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
             int maxWordLength, int maxBigrams);
@@ -103,9 +107,9 @@
     }
 
     @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        if (mNativeDict == 0) return;
+    public ArrayList<SuggestedWordInfo> getBigrams(final WordComposer codes,
+            final CharSequence previousWord) {
+        if (mNativeDict == 0) return null;
 
         int[] codePoints = StringUtils.toCodePointArray(previousWord.toString());
         Arrays.fill(mOutputChars_bigrams, (char) 0);
@@ -123,6 +127,7 @@
             count = MAX_BIGRAMS;
         }
 
+        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
         for (int j = 0; j < count; ++j) {
             if (codesSize > 0 && mBigramScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
@@ -131,19 +136,22 @@
                 ++len;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
-                        mDicTypeId, Dictionary.BIGRAM);
+                suggestions.add(new SuggestedWordInfo(
+                        new String(mOutputChars_bigrams, start, len),
+                        mBigramScores[j], SuggestedWordInfo.KIND_CORRECTION));
             }
         }
+        return suggestions;
     }
 
     // proximityInfo and/or prevWordForBigrams may not be null.
     @Override
-    public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams,
-            final WordCallback callback, final ProximityInfo proximityInfo) {
+    public ArrayList<SuggestedWordInfo> getWords(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
         final int count = getSuggestions(codes, prevWordForBigrams, proximityInfo, mOutputChars,
-                mScores);
+                mScores, mSpaceIndices);
 
+        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
         for (int j = 0; j < count; ++j) {
             if (mScores[j] < 1) break;
             final int start = j * MAX_WORD_LENGTH;
@@ -152,10 +160,13 @@
                 ++len;
             }
             if (len > 0) {
-                callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
-                        Dictionary.UNIGRAM);
+                // TODO: actually get the kind from native code
+                suggestions.add(new SuggestedWordInfo(
+                        new String(mOutputChars, start, len),
+                        mScores[j], SuggestedWordInfo.KIND_CORRECTION));
             }
         }
+        return suggestions;
     }
 
     /* package for test */ boolean isValidDictionary() {
@@ -165,7 +176,7 @@
     // proximityInfo may not be null.
     /* package for test */ int getSuggestions(final WordComposer codes,
             final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo,
-            char[] outputChars, int[] scores) {
+            char[] outputChars, int[] scores, int[] spaceIndices) {
         if (!isValidDictionary()) return -1;
 
         final int codesSize = codes.size();
@@ -179,14 +190,21 @@
         Arrays.fill(outputChars, (char) 0);
         Arrays.fill(scores, 0);
 
-        final int[] prevWordCodePointArray = null == prevWordForBigrams
+        // TODO: toLowerCase in the native code
+        final int[] prevWordCodePointArray = (null == prevWordForBigrams)
                 ? null : StringUtils.toCodePointArray(prevWordForBigrams.toString());
 
-        // TODO: pass the previous word to native code
-        return getSuggestionsNative(
-                mNativeDict, proximityInfo.getNativeProximityInfo(),
-                codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
-                prevWordCodePointArray, mUseFullEditDistance, outputChars, scores);
+        int[] emptyArray = new int[codesSize];
+        Arrays.fill(emptyArray, 0);
+
+        //final int commitPoint = codes.getCommitPoint();
+        //codes.clearCommitPoint();
+
+        return getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
+            codes.getXCoordinates(), codes.getYCoordinates(), emptyArray, emptyArray, mInputCodes,
+            codesSize, 0 /* unused */, false, mDicTypeId,
+            prevWordCodePointArray, mUseFullEditDistance,
+            outputChars, scores, spaceIndices);
     }
 
     public static float calcNormalizedScore(String before, String after, int score) {
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 10e511e..2a02603 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -120,12 +120,6 @@
         }
     }
 
-    @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
-        super.getBigrams(codes, previousWord, callback);
-    }
-
     private boolean useFirstLastBigramsForLocale(Locale locale) {
         // TODO: Add firstname/lastname bigram rules for other languages.
         if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 9c3d46e..99a04da 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -17,6 +17,9 @@
 package com.android.inputmethod.latin;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.ArrayList;
 
 /**
  * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
@@ -32,51 +35,26 @@
     public static final int BIGRAM = 1;
 
     public static final int NOT_A_PROBABILITY = -1;
-    /**
-     * Interface to be implemented by classes requesting words to be fetched from the dictionary.
-     * @see #getWords(WordComposer, CharSequence, WordCallback, ProximityInfo)
-     */
-    public interface WordCallback {
-        /**
-         * Adds a word to a list of suggestions. The word is expected to be ordered based on
-         * the provided score.
-         * @param word the character array containing the word
-         * @param wordOffset starting offset of the word in the character array
-         * @param wordLength length of valid characters in the character array
-         * @param score the score of occurrence. This is normalized between 1 and 255, but
-         * can exceed those limits
-         * @param dicTypeId of the dictionary where word was from
-         * @param dataType tells type of this data, either UNIGRAM or BIGRAM
-         * @return true if the word was added, false if no more words are required
-         */
-        boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
-                int dataType);
-    }
 
     /**
      * Searches for words in the dictionary that match the characters in the composer. Matched
-     * words are added through the callback object.
-     * @param composer the key sequence to match
+     * words are returned as an ArrayList.
+     * @param composer the key sequence to match with coordinate info, as a WordComposer
      * @param prevWordForBigrams the previous word, or null if none
-     * @param callback the callback object to send matched words to as possible candidates
      * @param proximityInfo the object for key proximity. May be ignored by some implementations.
-     * @see WordCallback#addWord(char[], int, int, int, int, int)
+     * @return the list of suggestions
      */
-    abstract public void getWords(final WordComposer composer,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo);
+    abstract public ArrayList<SuggestedWordInfo> getWords(final WordComposer composer,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo);
 
     /**
-     * Searches for pairs in the bigram dictionary that matches the previous word and all the
-     * possible words following are added through the callback object.
+     * Searches for pairs in the bigram dictionary that matches the previous word.
      * @param composer the key sequence to match
      * @param previousWord the word before
-     * @param callback the callback object to send possible word following previous word
+     * @return the list of suggestions
      */
-    public void getBigrams(final WordComposer composer, final CharSequence previousWord,
-            final WordCallback callback) {
-        // empty base implementation
-    }
+    public abstract ArrayList<SuggestedWordInfo> getBigrams(final WordComposer composer,
+            final CharSequence previousWord);
 
     /**
      * Checks if the given word occurs in the dictionary
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 26c2e63..169e707 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -17,9 +17,11 @@
 package com.android.inputmethod.latin;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -50,17 +52,41 @@
     }
 
     @Override
-    public void getWords(final WordComposer composer, final CharSequence prevWordForBigrams,
-            final WordCallback callback, final ProximityInfo proximityInfo) {
-        for (final Dictionary dict : mDictionaries)
-            dict.getWords(composer, prevWordForBigrams, callback, proximityInfo);
+    public ArrayList<SuggestedWordInfo> getWords(final WordComposer composer,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
+        final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
+        if (dictionaries.isEmpty()) return null;
+        // To avoid creating unnecessary objects, we get the list out of the first
+        // dictionary and add the rest to it if not null, hence the get(0)
+        ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getWords(composer,
+                prevWordForBigrams, proximityInfo);
+        if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
+        final int length = dictionaries.size();
+        for (int i = 0; i < length; ++ i) {
+            final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getWords(composer,
+                    prevWordForBigrams, proximityInfo);
+            if (null != sugg) suggestions.addAll(sugg);
+        }
+        return suggestions;
     }
 
     @Override
-    public void getBigrams(final WordComposer composer, final CharSequence previousWord,
-            final WordCallback callback) {
-        for (final Dictionary dict : mDictionaries)
-            dict.getBigrams(composer, previousWord, callback);
+    public ArrayList<SuggestedWordInfo> getBigrams(final WordComposer composer,
+            final CharSequence previousWord) {
+        final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
+        if (dictionaries.isEmpty()) return null;
+        // To avoid creating unnecessary objects, we get the list out of the first
+        // dictionary and add the rest to it if not null, hence the get(0)
+        ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getBigrams(composer,
+                previousWord);
+        if (null == suggestions) suggestions = new ArrayList<SuggestedWordInfo>();
+        final int length = dictionaries.size();
+        for (int i = 0; i < length; ++ i) {
+            final ArrayList<SuggestedWordInfo> sugg =
+                   dictionaries.get(i).getBigrams(composer, previousWord);
+            if (null != sugg) suggestions.addAll(sugg);
+        }
+        return suggestions;
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index a22d73a..6d77c4d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -59,7 +59,7 @@
             for (final AssetFileAddress f : assetFileList) {
                 final BinaryDictionary binaryDictionary =
                         new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength,
-                                useFullEditDistance, locale);
+                                useFullEditDistance, locale, Suggest.DIC_MAIN);
                 if (binaryDictionary.isValidDictionary()) {
                     dictList.add(binaryDictionary);
                 }
@@ -112,7 +112,7 @@
                 return null;
             }
             return new BinaryDictionary(context, sourceDir, afd.getStartOffset(), afd.getLength(),
-                    false /* useFullEditDistance */, locale);
+                    false /* useFullEditDistance */, locale, Suggest.DIC_MAIN);
         } catch (android.content.res.Resources.NotFoundException e) {
             Log.e(TAG, "Could not find the resource");
             return null;
@@ -140,7 +140,7 @@
             long startOffset, long length, final boolean useFullEditDistance, Locale locale) {
         if (dictionary.isFile()) {
             return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
-                    useFullEditDistance, locale);
+                    useFullEditDistance, locale, Suggest.DIC_MAIN);
         } else {
             Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
             return null;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index c65404c..c076fa0 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -19,6 +19,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
@@ -194,46 +195,47 @@
     }
 
     @Override
-    public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams,
-            final WordCallback callback, final ProximityInfo proximityInfo) {
+    public ArrayList<SuggestedWordInfo> getWords(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
         asyncReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
+        return getWordsInner(codes, prevWordForBigrams, proximityInfo);
     }
 
-    protected final void getWordsInner(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
+    protected final ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
         // Ensure that there are no concurrent calls to getWords. If there are, do nothing and
         // return.
         if (mLocalDictionaryController.tryLock()) {
             try {
                 if (mBinaryDictionary != null) {
-                    mBinaryDictionary.getWords(codes, prevWordForBigrams, callback, proximityInfo);
+                    return mBinaryDictionary.getWords(codes, prevWordForBigrams, proximityInfo);
                 }
             } finally {
                 mLocalDictionaryController.unlock();
             }
         }
+        return null;
     }
 
     @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
+    public ArrayList<SuggestedWordInfo> getBigrams(final WordComposer codes,
+            final CharSequence previousWord) {
         asyncReloadDictionaryIfRequired();
-        getBigramsInner(codes, previousWord, callback);
+        return getBigramsInner(codes, previousWord);
     }
 
-    protected void getBigramsInner(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
+    protected ArrayList<SuggestedWordInfo> getBigramsInner(final WordComposer codes,
+            final CharSequence previousWord) {
         if (mLocalDictionaryController.tryLock()) {
             try {
                 if (mBinaryDictionary != null) {
-                    mBinaryDictionary.getBigrams(codes, previousWord, callback);
+                    return mBinaryDictionary.getBigrams(codes, previousWord);
                 }
             } finally {
                 mLocalDictionaryController.unlock();
             }
         }
+        return null;
     }
 
     @Override
@@ -306,7 +308,7 @@
         // Build the new binary dictionary
         final BinaryDictionary newBinaryDictionary =
                 new BinaryDictionary(mContext, filename, 0, length, true /* useFullEditDistance */,
-                        null);
+                        null, mDicTypeId);
 
         if (mBinaryDictionary != null) {
             // Ensure all threads accessing the current dictionary have finished before swapping in
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 4a5471c..f19d77b 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -21,6 +21,7 @@
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
 
 import java.util.ArrayList;
@@ -247,23 +248,25 @@
     }
 
     @Override
-    public void getWords(final WordComposer codes, final CharSequence prevWordForBigrams,
-            final WordCallback callback, final ProximityInfo proximityInfo) {
+    public ArrayList<SuggestedWordInfo> getWords(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
         synchronized (mUpdatingLock) {
             // If we need to update, start off a background task
             if (mRequiresReload) startDictionaryLoadingTaskLocked();
             // Currently updating contacts, don't return any results.
-            if (mUpdatingDictionary) return;
+            if (mUpdatingDictionary) return null;
         }
         if (codes.size() >= BinaryDictionary.MAX_WORD_LENGTH) {
-            return;
+            return null;
         }
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
+        final ArrayList<SuggestedWordInfo> suggestions =
+                getWordsInner(codes, prevWordForBigrams, proximityInfo);
+        return suggestions;
     }
 
-    protected final void getWordsInner(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
+    protected final ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
+        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
         mInputLength = codes.size();
         if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
         final int[] xCoordinates = codes.getXCoordinates();
@@ -281,10 +284,11 @@
             proximityInfo.fillArrayWithNearestKeyCodes(x, y, codes.getCodeAt(i), mCodes[i]);
         }
         mMaxDepth = mInputLength * 3;
-        getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, callback);
+        getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, -1, suggestions);
         for (int i = 0; i < mInputLength; i++) {
-            getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, callback);
+            getWordsRec(mRoots, codes, mWordBuilder, 0, false, 1, 0, i, suggestions);
         }
+        return suggestions;
     }
 
     @Override
@@ -368,24 +372,27 @@
      * @param word the word to insert, as an array of code points
      * @param depth the depth of the node in the tree
      * @param finalFreq the frequency for this word
+     * @param suggestions the suggestion collection to add the suggestions to
      * @return whether there is still space for more words.
-     * @see Dictionary.WordCallback#addWord(char[], int, int, int, int, int)
      */
     private boolean addWordAndShortcutsFromNode(final Node node, final char[] word, final int depth,
-            final int finalFreq, final WordCallback callback) {
+            final int finalFreq, final ArrayList<SuggestedWordInfo> suggestions) {
         if (finalFreq > 0 && !node.mShortcutOnly) {
-            if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId, Dictionary.UNIGRAM)) {
-                return false;
-            }
+            // Use KIND_CORRECTION always. This dictionary does not really have a notion of
+            // COMPLETION against CORRECTION; we could artificially add one by looking at
+            // the respective size of the typed word and the suggestion if it matters sometime
+            // in the future.
+            suggestions.add(new SuggestedWordInfo(new String(word, 0, depth + 1), finalFreq,
+                    SuggestedWordInfo.KIND_CORRECTION));
+            if (suggestions.size() >= Suggest.MAX_SUGGESTIONS) return false;
         }
         if (null != node.mShortcutTargets) {
             final int length = node.mShortcutTargets.size();
             for (int shortcutIndex = 0; shortcutIndex < length; ++shortcutIndex) {
                 final char[] shortcut = node.mShortcutTargets.get(shortcutIndex);
-                if (!callback.addWord(shortcut, 0, shortcut.length, finalFreq, mDicTypeId,
-                        Dictionary.UNIGRAM)) {
-                    return false;
-                }
+                suggestions.add(new SuggestedWordInfo(new String(shortcut, 0, shortcut.length),
+                        finalFreq, SuggestedWordInfo.KIND_SHORTCUT));
+                if (suggestions.size() > Suggest.MAX_SUGGESTIONS) return false;
             }
         }
         return true;
@@ -408,12 +415,12 @@
      * case we skip over some punctuations such as apostrophe in the traversal. That is, if you type
      * "wouldve", it could be matching "would've", so the depth will be one more than the
      * inputIndex
-     * @param callback the callback class for adding a word
+     * @param suggestions the list in which to add suggestions
      */
     // TODO: Share this routine with the native code for BinaryDictionary
     protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word,
             final int depth, final boolean completion, int snr, int inputIndex, int skipPos,
-            WordCallback callback) {
+            final ArrayList<SuggestedWordInfo> suggestions) {
         final int count = roots.mLength;
         final int codeSize = mInputLength;
         // Optimization: Prune out words that are too long compared to how much was typed.
@@ -443,14 +450,14 @@
                     } else {
                         finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
                     }
-                    if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, callback)) {
+                    if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq, suggestions)) {
                         // No space left in the queue, bail out
                         return;
                     }
                 }
                 if (children != null) {
                     getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex,
-                            skipPos, callback);
+                            skipPos, suggestions);
                 }
             } else if ((c == Keyboard.CODE_SINGLE_QUOTE
                     && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) {
@@ -458,7 +465,7 @@
                 word[depth] = c;
                 if (children != null) {
                     getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
-                            skipPos, callback);
+                            skipPos, suggestions);
                 }
             } else {
                 // Don't use alternatives if we're looking for missing characters
@@ -483,7 +490,7 @@
                                             snr * addedAttenuation, mInputLength);
                                 }
                                 if (!addWordAndShortcutsFromNode(node, word, depth, finalFreq,
-                                        callback)) {
+                                        suggestions)) {
                                     // No space left in the queue, bail out
                                     return;
                                 }
@@ -491,12 +498,12 @@
                             if (children != null) {
                                 getWordsRec(children, codes, word, depth + 1,
                                         true, snr * addedAttenuation, inputIndex + 1,
-                                        skipPos, callback);
+                                        skipPos, suggestions);
                             }
                         } else if (children != null) {
                             getWordsRec(children, codes, word, depth + 1,
                                     false, snr * addedAttenuation, inputIndex + 1,
-                                    skipPos, callback);
+                                    skipPos, suggestions);
                         }
                     }
                 }
@@ -593,22 +600,25 @@
     }
 
     private void runBigramReverseLookUp(final CharSequence previousWord,
-            final WordCallback callback) {
+            final ArrayList<SuggestedWordInfo> suggestions) {
         // Search for the lowercase version of the word only, because that's where bigrams
         // store their sons.
         Node prevWord = searchNode(mRoots, previousWord.toString().toLowerCase(), 0,
                 previousWord.length());
         if (prevWord != null && prevWord.mNGrams != null) {
-            reverseLookUp(prevWord.mNGrams, callback);
+            reverseLookUp(prevWord.mNGrams, suggestions);
         }
     }
 
     @Override
-    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
-            final WordCallback callback) {
+    public ArrayList<SuggestedWordInfo> getBigrams(final WordComposer codes,
+            final CharSequence previousWord) {
         if (!reloadDictionaryIfRequired()) {
-            runBigramReverseLookUp(previousWord, callback);
+            final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<SuggestedWordInfo>();
+            runBigramReverseLookUp(previousWord, suggestions);
+            return suggestions;
         }
+        return null;
     }
 
     /**
@@ -635,11 +645,12 @@
 
     /**
      * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
-     * through callback.
+     * to the suggestions list passed as an argument.
      * @param terminalNodes list of terminal nodes we want to add
+     * @param suggestions the suggestion collection to add the word to
      */
     private void reverseLookUp(LinkedList<NextWord> terminalNodes,
-            final WordCallback callback) {
+            final ArrayList<SuggestedWordInfo> suggestions) {
         Node node;
         int freq;
         for (NextWord nextWord : terminalNodes) {
@@ -653,8 +664,9 @@
             } while (node != null);
 
             if (freq >= 0) {
-                callback.addWord(mLookedUpString, index, BinaryDictionary.MAX_WORD_LENGTH - index,
-                        freq, mDicTypeId, Dictionary.BIGRAM);
+                suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
+                        BinaryDictionary.MAX_WORD_LENGTH - index),
+                        freq, SuggestedWordInfo.KIND_CORRECTION));
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 8a5fc49..25b8fd5 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1710,7 +1710,7 @@
         // getSuggestedWords handles gracefully a null value of prevWord
         final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
                 prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(),
-                mCurrentSettings.mCorrectionEnabled);
+                mCurrentSettings.mCorrectionEnabled, false);
 
         // Basically, we update the suggestion strip only when suggestion count > 1.  However,
         // there is an exception: We update the suggestion strip whenever typed word's length
@@ -1922,7 +1922,9 @@
         if (mCurrentSettings.mCorrectionEnabled) {
             final CharSequence prevWord = mConnection.getThisWord(mCurrentSettings.mWordSeparators);
             if (!TextUtils.isEmpty(prevWord)) {
-                suggestedWords = mSuggest.getBigramPredictions(prevWord);
+                suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
+                        prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(),
+                        mCurrentSettings.mCorrectionEnabled, true);
             } else {
                 suggestedWords = null;
             }
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 0c19bed..40d327e 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -340,13 +340,6 @@
      * Returns the word before the cursor if the cursor is at the end of a word, null otherwise
      */
     public CharSequence getWordBeforeCursorIfAtEndOfWord(final SettingsValues settings) {
-        // Bail out if the cursor is not at the end of a word (cursor must be preceded by
-        // non-whitespace, non-separator, non-start-of-text)
-        // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
-        final CharSequence textBeforeCursor = getTextBeforeCursor(1, 0);
-        if (TextUtils.isEmpty(textBeforeCursor)
-                || settings.isWordSeparator(textBeforeCursor.charAt(0))) return null;
-
         // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
         // separator or end of line/text)
         // Example: "test|"<EOL> "te|st" get rejected here
@@ -363,6 +356,15 @@
             word = word.subSequence(1, word.length());
         }
         if (TextUtils.isEmpty(word)) return null;
+        // Find the last code point of the string
+        final int lastCodePoint = Character.codePointBefore(word, word.length());
+        // If for some reason the text field contains non-unicode binary data, or if the
+        // charsequence is exactly one char long and the contents is a low surrogate, return null.
+        if (!Character.isDefined(lastCodePoint)) return null;
+        // Bail out if the cursor is not at the end of a word (cursor must be preceded by
+        // non-whitespace, non-separator, non-start-of-text)
+        // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
+        if (settings.isWordSeparator(lastCodePoint)) return null;
         final char firstChar = word.charAt(0); // we just tested that word is not empty
         if (word.length() == 1 && !Character.isLetter(firstChar)) return null;
 
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 4c67b49..4c89a6e 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -143,19 +143,39 @@
             generalSettings.removePreference(mVoicePreference);
         }
 
+        final PreferenceGroup advancedSettings =
+                (PreferenceGroup) findPreference(PREF_ADVANCED_SETTINGS);
         if (!VibratorUtils.getInstance(context).hasVibrator()) {
-            final PreferenceGroup advancedSettings =
-                    (PreferenceGroup) findPreference(PREF_ADVANCED_SETTINGS);
             generalSettings.removePreference(findPreference(PREF_VIBRATE_ON));
             if (null != advancedSettings) { // Theoretically advancedSettings cannot be null
                 advancedSettings.removePreference(findPreference(PREF_VIBRATION_DURATION_SETTINGS));
             }
         }
 
-        final boolean showPopupOption = res.getBoolean(
+        final boolean showKeyPreviewPopupOption = res.getBoolean(
                 R.bool.config_enable_show_popup_on_keypress_option);
-        if (!showPopupOption) {
+        mKeyPreviewPopupDismissDelay =
+                (ListPreference) findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
+        if (!showKeyPreviewPopupOption) {
             generalSettings.removePreference(findPreference(PREF_POPUP_ON));
+            if (null != advancedSettings) { // Theoretically advancedSettings cannot be null
+                advancedSettings.removePreference(mKeyPreviewPopupDismissDelay);
+            }
+        } else {
+            final String[] entries = new String[] {
+                    res.getString(R.string.key_preview_popup_dismiss_no_delay),
+                    res.getString(R.string.key_preview_popup_dismiss_default_delay),
+            };
+            final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
+                    R.integer.config_key_preview_linger_timeout));
+            mKeyPreviewPopupDismissDelay.setEntries(entries);
+            mKeyPreviewPopupDismissDelay.setEntryValues(
+                    new String[] { "0", popupDismissDelayDefaultValue });
+            if (null == mKeyPreviewPopupDismissDelay.getValue()) {
+                mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
+            }
+            mKeyPreviewPopupDismissDelay.setEnabled(
+                    SettingsValues.isKeyPreviewPopupEnabled(prefs, res));
         }
 
         final CheckBoxPreference includeOtherImesInLanguageSwitchList =
@@ -163,23 +183,6 @@
         includeOtherImesInLanguageSwitchList.setEnabled(
                 !SettingsValues.isLanguageSwitchKeySupressed(prefs));
 
-        mKeyPreviewPopupDismissDelay =
-                (ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
-        final String[] entries = new String[] {
-                res.getString(R.string.key_preview_popup_dismiss_no_delay),
-                res.getString(R.string.key_preview_popup_dismiss_default_delay),
-        };
-        final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
-                R.integer.config_key_preview_linger_timeout));
-        mKeyPreviewPopupDismissDelay.setEntries(entries);
-        mKeyPreviewPopupDismissDelay.setEntryValues(
-                new String[] { "0", popupDismissDelayDefaultValue });
-        if (null == mKeyPreviewPopupDismissDelay.getValue()) {
-            mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
-        }
-        mKeyPreviewPopupDismissDelay.setEnabled(
-                SettingsValues.isKeyPreviewPopupEnabled(prefs, res));
-
         final PreferenceScreen dictionaryLink =
                 (PreferenceScreen) findPreference(PREF_CONFIGURE_DICTIONARIES_KEY);
         final Intent intent = dictionaryLink.getIntent();
@@ -305,13 +308,15 @@
 
     private void updateKeyPreviewPopupDelaySummary() {
         final ListPreference lp = mKeyPreviewPopupDismissDelay;
-        lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]);
+        final CharSequence[] entries = lp.getEntries();
+        if (entries == null || entries.length <= 0) return;
+        lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]);
     }
 
     private void updateVoiceModeSummary() {
         mVoicePreference.setSummary(
                 getResources().getStringArray(R.array.voice_input_modes_summary)
-                [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
+                        [mVoicePreference.findIndexOfValue(mVoicePreference.getValue())]);
     }
 
     private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index a43b905..6e7d985 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -184,6 +184,9 @@
         final char[] characters = string.toCharArray();
         final int length = characters.length;
         final int[] codePoints = new int[Character.codePointCount(characters, 0, length)];
+        if (length <= 0) {
+            return new int[0];
+        }
         int codePoint = Character.codePointAt(characters, 0);
         int dsti = 0;
         for (int srci = Character.charCount(codePoint);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 8922454..03ed9d9 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -26,6 +26,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.concurrent.ConcurrentHashMap;
@@ -34,7 +35,7 @@
  * This class loads a dictionary and provides a list of suggestions for a given sequence of
  * characters. This includes corrections and completions.
  */
-public class Suggest implements Dictionary.WordCallback {
+public class Suggest {
     public static final String TAG = Suggest.class.getSimpleName();
 
     public static final int APPROX_MAX_WORD_LENGTH = 32;
@@ -58,30 +59,34 @@
     public static final String DICT_KEY_CONTACTS = "contacts";
     // User dictionary, the system-managed one.
     public static final String DICT_KEY_USER = "user";
-    // User history dictionary for the unigram map, internal to LatinIME
-    public static final String DICT_KEY_USER_HISTORY_UNIGRAM = "history_unigram";
-    // User history dictionary for the bigram map, internal to LatinIME
-    public static final String DICT_KEY_USER_HISTORY_BIGRAM = "history_bigram";
+    // User history dictionary internal to LatinIME
+    public static final String DICT_KEY_USER_HISTORY = "history";
     public static final String DICT_KEY_WHITELIST ="whitelist";
+    // TODO: remove this map. This only serves as backward compatibility with a feature
+    // that has never been used and has been broken for a while.
+    private static final HashMap<String, Integer> sDictKeyToDictIndex
+            = new HashMap<String, Integer>();
+    static {
+        sDictKeyToDictIndex.put(DICT_KEY_MAIN, DIC_MAIN);
+        sDictKeyToDictIndex.put(DICT_KEY_USER, DIC_USER);
+        sDictKeyToDictIndex.put(DICT_KEY_USER_HISTORY, DIC_USER_HISTORY);
+        sDictKeyToDictIndex.put(DICT_KEY_CONTACTS, DIC_CONTACTS);
+        sDictKeyToDictIndex.put(DICT_KEY_WHITELIST, DIC_WHITELIST);
+    }
 
     private static final boolean DBG = LatinImeLogger.sDBG;
 
     private Dictionary mMainDictionary;
     private ContactsBinaryDictionary mContactsDict;
     private WhitelistDictionary mWhiteListDictionary;
-    private final ConcurrentHashMap<String, Dictionary> mUnigramDictionaries =
-            new ConcurrentHashMap<String, Dictionary>();
-    private final ConcurrentHashMap<String, Dictionary> mBigramDictionaries =
+    private final ConcurrentHashMap<String, Dictionary> mDictionaries =
             new ConcurrentHashMap<String, Dictionary>();
 
     public static final int MAX_SUGGESTIONS = 18;
 
-    private static final int PREF_MAX_BIGRAMS = 60;
-
     private float mAutoCorrectionThreshold;
 
     private ArrayList<SuggestedWordInfo> mSuggestions = new ArrayList<SuggestedWordInfo>();
-    private ArrayList<SuggestedWordInfo> mBigramSuggestions = new ArrayList<SuggestedWordInfo>();
     private CharSequence mConsideredWord;
 
     // TODO: Remove these member variables by passing more context to addWord() callback method
@@ -100,14 +105,13 @@
         final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
                 startOffset, length /* useFullEditDistance */, false, locale);
         mMainDictionary = mainDict;
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict);
-        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict);
+        addOrReplaceDictionary(mDictionaries, DICT_KEY_MAIN, mainDict);
         initWhitelistAndAutocorrectAndPool(context, locale);
     }
 
     private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
         mWhiteListDictionary = new WhitelistDictionary(context, locale);
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
+        addOrReplaceDictionary(mDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
     }
 
     private void initAsynchronously(final Context context, final Locale locale) {
@@ -136,8 +140,7 @@
             public void run() {
                 final DictionaryCollection newMainDict =
                         DictionaryFactory.createMainDictionaryFromManager(context, locale);
-                addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict);
-                addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict);
+                addOrReplaceDictionary(mDictionaries, DICT_KEY_MAIN, newMainDict);
                 mMainDictionary = newMainDict;
             }
         }.start();
@@ -158,7 +161,7 @@
     }
 
     public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
-        return mUnigramDictionaries;
+        return mDictionaries;
     }
 
     public static int getApproxMaxWordLength() {
@@ -170,7 +173,7 @@
      * before the main dictionary, if set. This refers to the system-managed user dictionary.
      */
     public void setUserDictionary(UserBinaryDictionary userDictionary) {
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary);
+        addOrReplaceDictionary(mDictionaries, DICT_KEY_USER, userDictionary);
     }
 
     /**
@@ -180,15 +183,11 @@
      */
     public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) {
         mContactsDict = contactsDictionary;
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
-        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
+        addOrReplaceDictionary(mDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
     }
 
     public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) {
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_HISTORY_UNIGRAM,
-                userHistoryDictionary);
-        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_HISTORY_BIGRAM,
-                userHistoryDictionary);
+        addOrReplaceDictionary(mDictionaries, DICT_KEY_USER_HISTORY, userHistoryDictionary);
     }
 
     public void setAutoCorrectionThreshold(float threshold) {
@@ -212,50 +211,14 @@
         return sb;
     }
 
-    protected void addBigramToSuggestions(SuggestedWordInfo bigram) {
-        mSuggestions.add(bigram);
-    }
-
-    private static final WordComposer sEmptyWordComposer = new WordComposer();
-    public SuggestedWords getBigramPredictions(CharSequence prevWordForBigram) {
-        LatinImeLogger.onStartSuggestion(prevWordForBigram);
-        mIsFirstCharCapitalized = false;
-        mIsAllUpperCase = false;
-        mTrailingSingleQuotesCount = 0;
-        mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS);
-
-        // Treating USER_TYPED as UNIGRAM suggestion for logging now.
-        LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
-        mConsideredWord = "";
-
-        mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
-
-        getAllBigrams(prevWordForBigram, sEmptyWordComposer);
-
-        // Nothing entered: return all bigrams for the previous word
-        int insertCount = Math.min(mBigramSuggestions.size(), MAX_SUGGESTIONS);
-        for (int i = 0; i < insertCount; ++i) {
-            addBigramToSuggestions(mBigramSuggestions.get(i));
-        }
-
-        SuggestedWordInfo.removeDups(mSuggestions);
-
-        return new SuggestedWords(mSuggestions,
-                false /* typedWordValid */,
-                false /* hasAutoCorrectionCandidate */,
-                false /* allowsToBeAutoCorrected */,
-                false /* isPunctuationSuggestions */,
-                false /* isObsoleteSuggestions */,
-                true /* isPrediction */);
-    }
-
     // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
     public SuggestedWords getSuggestedWords(
             final WordComposer wordComposer, CharSequence prevWordForBigram,
-            final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
+            final ProximityInfo proximityInfo, final boolean isCorrectionEnabled,
+            final boolean isPrediction) {
         LatinImeLogger.onStartSuggestion(prevWordForBigram);
-        mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
-        mIsAllUpperCase = wordComposer.isAllUpperCase();
+        mIsFirstCharCapitalized = !isPrediction && wordComposer.isFirstCharCapitalized();
+        mIsAllUpperCase = !isPrediction && wordComposer.isAllUpperCase();
         mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
         mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS);
 
@@ -269,37 +232,29 @@
 
         if (wordComposer.size() <= 1 && isCorrectionEnabled) {
             // At first character typed, search only the bigrams
-            mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
-
             if (!TextUtils.isEmpty(prevWordForBigram)) {
-                getAllBigrams(prevWordForBigram, wordComposer);
-                if (TextUtils.isEmpty(consideredWord)) {
-                    // Nothing entered: return all bigrams for the previous word
-                    int insertCount = Math.min(mBigramSuggestions.size(), MAX_SUGGESTIONS);
-                    for (int i = 0; i < insertCount; ++i) {
-                        addBigramToSuggestions(mBigramSuggestions.get(i));
-                    }
-                } else {
-                    // Word entered: return only bigrams that match the first char of the typed word
-                    final char currentChar = consideredWord.charAt(0);
+                final CharSequence lowerPrevWord;
+                if (StringUtils.hasUpperCase(prevWordForBigram)) {
                     // TODO: Must pay attention to locale when changing case.
-                    // TODO: Use codepoint instead of char
-                    final char currentCharUpper = Character.toUpperCase(currentChar);
-                    int count = 0;
-                    final int bigramSuggestionSize = mBigramSuggestions.size();
-                    for (int i = 0; i < bigramSuggestionSize; i++) {
-                        final SuggestedWordInfo bigramSuggestion = mBigramSuggestions.get(i);
-                        final char bigramSuggestionFirstChar =
-                                (char)bigramSuggestion.codePointAt(0);
-                        if (bigramSuggestionFirstChar == currentChar
-                                || bigramSuggestionFirstChar == currentCharUpper) {
-                            addBigramToSuggestions(bigramSuggestion);
-                            if (++count > MAX_SUGGESTIONS) break;
-                        }
+                    lowerPrevWord = prevWordForBigram.toString().toLowerCase();
+                } else {
+                    lowerPrevWord = null;
+                }
+                for (final String key : mDictionaries.keySet()) {
+                    final int dicTypeId = sDictKeyToDictIndex.get(key);
+                    final Dictionary dictionary = mDictionaries.get(key);
+                    final ArrayList<SuggestedWordInfo> suggestions =
+                            dictionary.getBigrams(wordComposer, prevWordForBigram);
+                    if (null != lowerPrevWord) {
+                        suggestions.addAll(dictionary.getBigrams(wordComposer, lowerPrevWord));
+                    }
+                    for (final SuggestedWordInfo suggestion : suggestions) {
+                        final String suggestionStr = suggestion.mWord.toString();
+                        addWord(suggestionStr.toCharArray(), null, 0, suggestionStr.length(),
+                                suggestion.mScore, dicTypeId, Dictionary.BIGRAM, mSuggestions);
                     }
                 }
             }
-
         } else if (wordComposer.size() > 1) {
             final WordComposer wordComposerForLookup;
             if (mTrailingSingleQuotesCount > 0) {
@@ -311,12 +266,19 @@
                 wordComposerForLookup = wordComposer;
             }
             // At second character typed, search the unigrams (scores being affected by bigrams)
-            for (final String key : mUnigramDictionaries.keySet()) {
+            for (final String key : mDictionaries.keySet()) {
                 // Skip UserUnigramDictionary and WhitelistDictionary to lookup
-                if (key.equals(DICT_KEY_USER_HISTORY_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
+                if (key.equals(DICT_KEY_USER_HISTORY) || key.equals(DICT_KEY_WHITELIST))
                     continue;
-                final Dictionary dictionary = mUnigramDictionaries.get(key);
-                dictionary.getWords(wordComposerForLookup, prevWordForBigram, this, proximityInfo);
+                final int dicTypeId = sDictKeyToDictIndex.get(key);
+                final Dictionary dictionary = mDictionaries.get(key);
+                final ArrayList<SuggestedWordInfo> suggestions = dictionary.getWords(
+                        wordComposerForLookup, prevWordForBigram, proximityInfo);
+                for (final SuggestedWordInfo suggestion : suggestions) {
+                    final String suggestionStr = suggestion.mWord.toString();
+                    addWord(suggestionStr.toCharArray(), null, 0, suggestionStr.length(),
+                            suggestion.mScore, dicTypeId, Dictionary.UNIGRAM, mSuggestions);
+                }
             }
         }
 
@@ -326,7 +288,7 @@
         final boolean hasAutoCorrection;
         if (isCorrectionEnabled) {
             final CharSequence autoCorrection =
-                    AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer,
+                    AutoCorrection.computeAutoCorrectionWord(mDictionaries, wordComposer,
                             mSuggestions, consideredWord, mAutoCorrectionThreshold,
                             whitelistedWord);
             hasAutoCorrection = (null != autoCorrection);
@@ -348,12 +310,14 @@
             }
         }
 
-        mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
-                SuggestedWordInfo.KIND_TYPED));
+        if (!isPrediction) {
+            mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
+                    SuggestedWordInfo.KIND_TYPED));
+        }
         SuggestedWordInfo.removeDups(mSuggestions);
 
         final ArrayList<SuggestedWordInfo> suggestionsList;
-        if (DBG) {
+        if (DBG && !mSuggestions.isEmpty()) {
             suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, mSuggestions);
         } else {
             suggestionsList = mSuggestions;
@@ -386,29 +350,12 @@
             autoCorrectionAvailable = false;
         }
         return new SuggestedWords(suggestionsList,
-                !allowsToBeAutoCorrected /* typedWordValid */,
-                autoCorrectionAvailable /* hasAutoCorrectionCandidate */,
-                allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
+                !isPrediction && !allowsToBeAutoCorrected /* typedWordValid */,
+                !isPrediction && autoCorrectionAvailable /* hasAutoCorrectionCandidate */,
+                !isPrediction && allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
                 false /* isPunctuationSuggestions */,
                 false /* isObsoleteSuggestions */,
-                false /* isPrediction */);
-    }
-
-    /**
-     * Adds all bigram predictions for prevWord. Also checks the lower case version of prevWord if
-     * it contains any upper case characters.
-     */
-    private void getAllBigrams(final CharSequence prevWord, final WordComposer wordComposer) {
-        if (StringUtils.hasUpperCase(prevWord)) {
-            // TODO: Must pay attention to locale when changing case.
-            final CharSequence lowerPrevWord = prevWord.toString().toLowerCase();
-            for (final Dictionary dictionary : mBigramDictionaries.values()) {
-                dictionary.getBigrams(wordComposer, lowerPrevWord, this);
-            }
-        }
-        for (final Dictionary dictionary : mBigramDictionaries.values()) {
-            dictionary.getBigrams(wordComposer, prevWord, this);
-        }
+                isPrediction);
     }
 
     private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
@@ -438,19 +385,11 @@
     }
 
     // TODO: Use codepoint instead of char
-    @Override
-    public boolean addWord(final char[] word, final int offset, final int length, int score,
-            final int dicTypeId, final int dataType) {
+    public boolean addWord(final char[] word, int[] indices, final int offset, final int length,
+            int score, final int dicTypeId, final int dataType,
+            final ArrayList<SuggestedWordInfo> suggestions) {
         int dataTypeForLog = dataType;
-        final ArrayList<SuggestedWordInfo> suggestions;
-        final int prefMaxSuggestions;
-        if (dataType == Dictionary.BIGRAM) {
-            suggestions = mBigramSuggestions;
-            prefMaxSuggestions = PREF_MAX_BIGRAMS;
-        } else {
-            suggestions = mSuggestions;
-            prefMaxSuggestions = MAX_SUGGESTIONS;
-        }
+        final int prefMaxSuggestions = MAX_SUGGESTIONS;
 
         int pos = 0;
 
@@ -513,8 +452,7 @@
 
     public void close() {
         final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
-        dictionaries.addAll(mUnigramDictionaries.values());
-        dictionaries.addAll(mBigramDictionaries.values());
+        dictionaries.addAll(mDictionaries.values());
         for (final Dictionary dictionary : dictionaries) {
             dictionary.close();
         }
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 1ed91fe..45ac9ff 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -129,10 +129,11 @@
         public static final int KIND_BLACKLIST = 4; // Blacklisted word
         public static final int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation
         public static final int KIND_APP_DEFINED = 6; // Suggested by the application
+        public static final int KIND_SHORTCUT = 7; // A shortcut
         private final String mWordStr;
         public final CharSequence mWord;
         public final int mScore;
-        public final int mKind;
+        public final int mKind; // one of the KIND_* constants above
         public final int mCodePointCount;
         private String mDebugString = "";
 
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
index 673b545..74f27e3 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
+import java.util.ArrayList;
 import java.util.Locale;
 
 public class SynchronouslyLoadedContactsBinaryDictionary extends ContactsBinaryDictionary {
@@ -30,11 +32,10 @@
     }
 
     @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
+    public synchronized ArrayList<SuggestedWordInfo> getWords(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
         syncReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
+        return getWordsInner(codes, prevWordForBigrams, proximityInfo);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
index 1606a34..5b2a6ed 100644
--- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java
@@ -19,6 +19,9 @@
 import android.content.Context;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
+import java.util.ArrayList;
 
 public class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDictionary {
 
@@ -32,11 +35,10 @@
     }
 
     @Override
-    public synchronized void getWords(final WordComposer codes,
-            final CharSequence prevWordForBigrams, final WordCallback callback,
-            final ProximityInfo proximityInfo) {
+    public synchronized ArrayList<SuggestedWordInfo> getWords(final WordComposer codes,
+            final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) {
         syncReloadDictionaryIfRequired();
-        getWordsInner(codes, prevWordForBigrams, callback, proximityInfo);
+        return getWordsInner(codes, prevWordForBigrams, proximityInfo);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 6fa1a25..5bcdb57 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -36,11 +36,22 @@
 
     // TODO: use Words.SHORTCUT when it's public in the SDK
     final static String SHORTCUT = "shortcut";
-    private static final String[] PROJECTION_QUERY = {
-        Words.WORD,
-        SHORTCUT,
-        Words.FREQUENCY,
-    };
+    private static final String[] PROJECTION_QUERY;
+    static {
+        // 16 is JellyBean, but we want this to compile against ICS.
+        if (android.os.Build.VERSION.SDK_INT >= 16) {
+            PROJECTION_QUERY = new String[] {
+                Words.WORD,
+                SHORTCUT,
+                Words.FREQUENCY,
+            };
+        } else {
+            PROJECTION_QUERY = new String[] {
+                Words.WORD,
+                Words.FREQUENCY,
+            };
+        }
+    }
 
     private static final String NAME = "userunigram";
 
@@ -136,7 +147,7 @@
             requestArguments = localeElements;
         }
         final Cursor cursor = mContext.getContentResolver().query(
-            Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
+                Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
         try {
             addWords(cursor);
         } finally {
@@ -182,16 +193,18 @@
     }
 
     private void addWords(Cursor cursor) {
+        // 16 is JellyBean, but we want this to compile against ICS.
+        final boolean hasShortcutColumn = android.os.Build.VERSION.SDK_INT >= 16;
         clearFusionDictionary();
         if (cursor == null) return;
         if (cursor.moveToFirst()) {
             final int indexWord = cursor.getColumnIndex(Words.WORD);
-            final int indexShortcut = cursor.getColumnIndex(SHORTCUT);
+            final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(SHORTCUT) : 0;
             final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
             while (!cursor.isAfterLast()) {
-                String word = cursor.getString(indexWord);
-                String shortcut = cursor.getString(indexShortcut);
-                int frequency = cursor.getInt(indexFrequency);
+                final String word = cursor.getString(indexWord);
+                final String shortcut = hasShortcutColumn ? cursor.getString(indexShortcut) : null;
+                final int frequency = cursor.getInt(indexFrequency);
                 // Safeguard against adding really long words.
                 if (word.length() < MAX_WORD_LENGTH) {
                     super.addWord(word, null, frequency);
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index f2d21ab..8f71de0 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -44,6 +44,7 @@
 import java.io.PrintWriter;
 import java.nio.channels.FileChannel;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
 
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 7fffc31..5f4d660 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -32,12 +32,12 @@
 import com.android.inputmethod.latin.BinaryDictionary;
 import com.android.inputmethod.latin.ContactsBinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.Dictionary.WordCallback;
 import com.android.inputmethod.latin.DictionaryCollection;
 import com.android.inputmethod.latin.DictionaryFactory;
 import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
 import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
 import com.android.inputmethod.latin.UserBinaryDictionary;
@@ -203,7 +203,8 @@
                 EMPTY_STRING_ARRAY);
     }
 
-    private static class SuggestionsGatherer implements WordCallback {
+    // TODO: remove this class and replace it by storage local to the session.
+    private static class SuggestionsGatherer {
         public static class Result {
             public final String[] mSuggestions;
             public final boolean mHasRecommendedSuggestions;
@@ -237,9 +238,8 @@
             mScores = new int[mMaxLength];
         }
 
-        @Override
-        synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
-                int dicTypeId, int dataType) {
+        synchronized public boolean addWord(char[] word, int[] spaceIndices, int wordOffset,
+                int wordLength, int score) {
             final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score);
             // binarySearch returns the index if the element exists, and -<insertion index> - 1
             // if it doesn't. See documentation for binarySearch.
@@ -780,8 +780,13 @@
                 try {
                     dictInfo = mDictionaryPool.takeOrGetNull();
                     if (null == dictInfo) return getNotInDictEmptySuggestions();
-                    dictInfo.mDictionary.getWords(composer, prevWord, suggestionsGatherer,
-                            dictInfo.mProximityInfo);
+                    final ArrayList<SuggestedWordInfo> suggestions = dictInfo.mDictionary.getWords(
+                            composer, prevWord, dictInfo.mProximityInfo);
+                    for (final SuggestedWordInfo suggestion : suggestions) {
+                        final String suggestionStr = suggestion.mWord.toString();
+                        suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
+                                suggestionStr.length(), suggestion.mScore);
+                    }
                     isInDict = dictInfo.mDictionary.isValidWord(text);
                     if (!isInDict && CAPITALIZE_NONE != capitalizeType) {
                         // We want to test the word again if it's all caps or first caps only.
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 3bb7b58..5d705b1 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -47,7 +47,8 @@
     dictionary.cpp \
     proximity_info.cpp \
     proximity_info_state.cpp \
-    unigram_dictionary.cpp
+    unigram_dictionary.cpp \
+    gesture/build_check.cpp
 
 LOCAL_SRC_FILES := \
     $(LATIN_IME_JNI_SRC_FILES) \
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index d10dc96..3fa45da 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -128,28 +128,37 @@
 
 static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
         jlong proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
-        jintArray inputArray, jint arraySize, jintArray prevWordForBigrams,
-        jboolean useFullEditDistance, jcharArray outputArray, jintArray frequencyArray) {
-    Dictionary *dictionary = (Dictionary*)dict;
+        jintArray timesArray, jintArray pointerIdArray, jintArray inputArray, jint arraySize,
+        jint commitPoint, jboolean isGesture, jint dicTypeId,
+        jintArray prevWordForBigrams, jboolean useFullEditDistance, jcharArray outputArray,
+        jintArray frequencyArray, jintArray spaceIndexArray) {
+    Dictionary *dictionary = (Dictionary*) dict;
     if (!dictionary) return 0;
     ProximityInfo *pInfo = (ProximityInfo*)proximityInfo;
     int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, 0);
     int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, 0);
+    int *times = env->GetIntArrayElements(timesArray, 0);
+    int *pointerIds = env->GetIntArrayElements(pointerIdArray, 0);
     int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
     int *inputCodes = env->GetIntArrayElements(inputArray, 0);
     jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
+    int *spaceIndices = env->GetIntArrayElements(spaceIndexArray, 0);
     jint *prevWordChars = prevWordForBigrams
             ? env->GetIntArrayElements(prevWordForBigrams, 0) : 0;
     jsize prevWordLength = prevWordChars ? env->GetArrayLength(prevWordForBigrams) : 0;
-    int count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, inputCodes,
-            arraySize, prevWordChars, prevWordLength, useFullEditDistance,
-            (unsigned short*) outputChars, frequencies);
+    int count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, times, pointerIds,
+            inputCodes, arraySize, prevWordChars, prevWordLength, commitPoint, isGesture,
+            dicTypeId, useFullEditDistance, (unsigned short*) outputChars,
+            frequencies, spaceIndices);
     if (prevWordChars) {
         env->ReleaseIntArrayElements(prevWordForBigrams, prevWordChars, JNI_ABORT);
     }
+    env->ReleaseIntArrayElements(spaceIndexArray, spaceIndices, 0);
     env->ReleaseCharArrayElements(outputArray, outputChars, 0);
     env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
     env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
+    env->ReleaseIntArrayElements(pointerIdArray, pointerIds, 0);
+    env->ReleaseIntArrayElements(timesArray, times, 0);
     env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0);
     env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0);
     return count;
@@ -251,8 +260,8 @@
 static JNINativeMethod sMethods[] = {
     {"openNative", "(Ljava/lang/String;JJIIII)J", (void*)latinime_BinaryDictionary_open},
     {"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
-    {"getSuggestionsNative", "(JJ[I[I[II[IZ[C[I)I",
-            (void*)latinime_BinaryDictionary_getSuggestions},
+    {"getSuggestionsNative", "(JJ[I[I[I[I[IIIZI[IZ[C[I[I)I",
+            (void*) latinime_BinaryDictionary_getSuggestions},
     {"getFrequencyNative", "(J[II)I", (void*)latinime_BinaryDictionary_getFrequency},
     {"isValidBigramNative", "(J[I[I)Z", (void*)latinime_BinaryDictionary_isValidBigram},
     {"getBigramsNative", "(J[II[II[C[III)I", (void*)latinime_BinaryDictionary_getBigrams},
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index 83bb267..e0b7f87 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -43,6 +43,8 @@
     mUnigramDictionary = new UnigramDictionary(mDict + headerSize, typedLetterMultiplier,
             fullWordMultiplier, maxWordLength, maxWords, options);
     mBigramDictionary = new BigramDictionary(mDict + headerSize, maxWordLength);
+    mGestureDecoder = new GestureDecoder(maxWordLength, maxWords);
+    mGestureDecoder->setDict(mUnigramDictionary, mBigramDictionary);
 }
 
 Dictionary::~Dictionary() {
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
index fd69f79..708cb09 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/dictionary.h
@@ -22,6 +22,7 @@
 #include "bigram_dictionary.h"
 #include "char_utils.h"
 #include "defines.h"
+#include "gesture/gesture_decoder.h"
 #include "proximity_info.h"
 #include "unigram_dictionary.h"
 #include "words_priority_queue_pool.h"
@@ -34,15 +35,26 @@
             int fullWordMultiplier, int maxWordLength, int maxWords);
 
     int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
-            int *codes, int codesSize, const int32_t* prevWordChars, const int prevWordLength,
-            bool useFullEditDistance, unsigned short *outWords, int *frequencies) const {
-        std::map<int, int> bigramMap;
-        uint8_t bigramFilter[BIGRAM_FILTER_BYTE_SIZE];
-        mBigramDictionary->fillBigramAddressToFrequencyMapAndFilter(prevWordChars,
-                prevWordLength, &bigramMap, bigramFilter);
-        return mUnigramDictionary->getSuggestions(proximityInfo,
-                xcoordinates, ycoordinates, codes, codesSize, &bigramMap,
-                bigramFilter, useFullEditDistance, outWords, frequencies);
+            int *times, int *pointerIds, int *codes, int codesSize, int *prevWordChars,
+            int prevWordLength, int commitPoint, bool isGesture, int dicTypeId,
+            bool useFullEditDistance, unsigned short *outWords,
+            int *frequencies, int *spaceIndices) {
+        int result = 0;
+        if (isGesture) {
+            mGestureDecoder->setPrevWord(prevWordChars, prevWordLength);
+            result = mGestureDecoder->getSuggestions(proximityInfo, xcoordinates, ycoordinates,
+                    times, pointerIds, codes, codesSize, commitPoint, dicTypeId == 1 /* main */,
+                    outWords, frequencies, spaceIndices);
+        } else {
+            std::map<int, int> bigramMap;
+            uint8_t bigramFilter[BIGRAM_FILTER_BYTE_SIZE];
+            mBigramDictionary->fillBigramAddressToFrequencyMapAndFilter(prevWordChars,
+                    prevWordLength, &bigramMap, bigramFilter);
+            result = mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates,
+                    ycoordinates, codes, codesSize, &bigramMap, bigramFilter,
+                    useFullEditDistance, outWords, frequencies);
+        }
+        return result;
     }
 
     int getBigrams(const int32_t *word, int length, int *codes, int codesSize,
@@ -75,6 +87,7 @@
 
     const UnigramDictionary *mUnigramDictionary;
     const BigramDictionary *mBigramDictionary;
+    GestureDecoder *mGestureDecoder;
 };
 
 // public static utility methods
diff --git a/native/jni/src/gesture/build_check.cpp b/native/jni/src/gesture/build_check.cpp
new file mode 100644
index 0000000..8ec94f5
--- /dev/null
+++ b/native/jni/src/gesture/build_check.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include "gesture_decoder.h"
+
+namespace latinime {
+};
+// namespace latinime
diff --git a/native/jni/src/gesture/gesture_decoder.h b/native/jni/src/gesture/gesture_decoder.h
new file mode 100644
index 0000000..8e79555
--- /dev/null
+++ b/native/jni/src/gesture/gesture_decoder.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef LATINIME_GESTURE_DECODER_H
+#define LATINIME_GESTURE_DECODER_H
+
+#include "defines.h"
+#include "gesture_decoder_impl.h"
+
+namespace latinime {
+
+class GestureDecoder : public GestureDecoderImpl {
+
+ public:
+    GestureDecoder(int maxWordLength, int maxWords) :
+            GestureDecoderImpl(maxWordLength, maxWords) {
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(GestureDecoder);
+};
+} // namespace latinime
+
+#endif // LATINIME_INCREMENTAL_DECODER_H
diff --git a/native/jni/src/gesture/gesture_decoder_impl.h b/native/jni/src/gesture/gesture_decoder_impl.h
new file mode 100644
index 0000000..be4e8b3
--- /dev/null
+++ b/native/jni/src/gesture/gesture_decoder_impl.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef LATINIME_GESTURE_DECODER_IMPL_H
+#define LATINIME_GESTURE_DECODER_IMPL_H
+
+#include "defines.h"
+#include "incremental_decoder.h"
+
+namespace latinime {
+
+class GestureDecoderImpl : public IncrementalDecoder {
+
+ public:
+    GestureDecoderImpl(int maxWordLength, int maxWords) :
+            IncrementalDecoder(maxWordLength, maxWords) {
+    }
+
+    int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
+            int *pointerIds, int *codes, int inputSize, int commitPoint, bool isMainDict,
+            unsigned short *outWords, int *frequencies, int *outputIndices) {
+        return 0;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(GestureDecoderImpl);
+};
+} // namespace latinime
+
+#endif // LATINIME_GESTURE_DECODER_IMPL_H
diff --git a/native/jni/src/gesture/incremental_decoder.h b/native/jni/src/gesture/incremental_decoder.h
new file mode 100644
index 0000000..fe93552
--- /dev/null
+++ b/native/jni/src/gesture/incremental_decoder.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef LATINIME_INCREMENTAL_DECODER_H
+#define LATINIME_INCREMENTAL_DECODER_H
+
+#include "defines.h"
+#include "incremental_decoder_impl.h"
+
+namespace latinime {
+
+class IncrementalDecoder : public IncrementalDecoderImpl {
+
+ public:
+     IncrementalDecoder(int maxWordLength, int maxWords) :
+             IncrementalDecoderImpl(maxWordLength, maxWords) {
+     }
+
+ private:
+     DISALLOW_IMPLICIT_CONSTRUCTORS(IncrementalDecoder);
+};
+} // namespace latinime
+
+#endif // LATINIME_INCREMENTAL_DECODER_H
diff --git a/native/jni/src/gesture/incremental_decoder_impl.h b/native/jni/src/gesture/incremental_decoder_impl.h
new file mode 100644
index 0000000..5731ad8
--- /dev/null
+++ b/native/jni/src/gesture/incremental_decoder_impl.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef LATINIME_INCREMENTAL_DECODER_IMPL_H
+#define LATINIME_INCREMENTAL_DECODER_IMPL_H
+
+#include "bigram_dictionary.h"
+#include "defines.h"
+#include "incremental_decoder_interface.h"
+#include "unigram_dictionary.h"
+
+namespace latinime {
+
+class IncrementalDecoderImpl : IncrementalDecoderInterface {
+
+ public:
+     IncrementalDecoderImpl(int maxWordLength, int maxWords) { };
+     void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram) { };
+     void setPrevWord(const int32_t *prevWord, int prevWordLength) { };
+     void reset() { };
+
+ private:
+     DISALLOW_IMPLICIT_CONSTRUCTORS(IncrementalDecoderImpl);
+};
+} // namespace latinime
+
+#endif // LATINIME_INCREMENTAL_DECODER_IMPL_H
diff --git a/native/jni/src/gesture/incremental_decoder_interface.h b/native/jni/src/gesture/incremental_decoder_interface.h
new file mode 100644
index 0000000..d34b0da
--- /dev/null
+++ b/native/jni/src/gesture/incremental_decoder_interface.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef LATINIME_INCREMENTAL_DECODER_INTERFACE_H
+#define LATINIME_INCREMENTAL_DECODER_INTERFACE_H
+
+#include "bigram_dictionary.h"
+#include "defines.h"
+#include "proximity_info.h"
+#include "unigram_dictionary.h"
+
+namespace latinime {
+
+class IncrementalDecoderInterface {
+
+ public:
+    virtual int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
+            int *pointerIds, int *codes, int inputSize, int commitPoint, bool isMainDict,
+            unsigned short *outWords, int *frequencies, int *outputIndices) = 0;
+    virtual void reset() = 0;
+    virtual void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram) = 0;
+    virtual void setPrevWord(const int32_t *prevWord, int prevWordLength) = 0;
+    virtual ~IncrementalDecoderInterface() { };
+};
+} // namespace latinime
+
+#endif // LATINIME_INCREMENTAL_DECODER_INTERFACE_H