From 1b62ff1a3d61cd44ab88acdfcbdf0fc70a7e1b10 Mon Sep 17 00:00:00 2001
From: Amith Yamasani <yamasani@google.com>
Date: Fri, 5 Feb 2010 14:07:04 -0800
Subject: [PATCH] Increase target size of preferred letters while typing.

This increases the chance of hitting the correct letter when typing a word
that exists in the dictionary, rather than only correct it after the fact.
It is most effective after 2 or 3 letters of a word have been typed and gets
more accurate with more typed letters in the word.

If 2 adjacent letters have similar probabilities of occuring, then there is no
hit correction applied.
---
 ...oid_inputmethod_latin_BinaryDictionary.cpp |   9 +-
 dictionary/src/dictionary.cpp                 |  26 ++++-
 dictionary/src/dictionary.h                   |   6 +-
 .../inputmethod/latin/BinaryDictionary.java   |  13 ++-
 .../inputmethod/latin/ContactsDictionary.java |   5 +-
 .../android/inputmethod/latin/Dictionary.java |   7 +-
 .../latin/ExpandableDictionary.java           |  10 +-
 .../android/inputmethod/latin/LatinIME.java   |   6 +
 .../inputmethod/latin/LatinKeyboard.java      | 110 +++++++++++++++++-
 .../inputmethod/latin/LatinKeyboardView.java  |  19 +++
 .../android/inputmethod/latin/Suggest.java    |  19 ++-
 .../inputmethod/latin/UserDictionary.java     |   5 +-
 12 files changed, 215 insertions(+), 20 deletions(-)

diff --git a/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 3076085e44..3dda062fe5 100644
--- a/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/dictionary/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -89,7 +89,7 @@ static jint latinime_BinaryDictionary_open
 static int latinime_BinaryDictionary_getSuggestions(
         JNIEnv *env, jobject object, jint dict, jintArray inputArray, jint arraySize,
         jcharArray outputArray, jintArray frequencyArray, jint maxWordLength, jint maxWords,
-        jint maxAlternatives, jint skipPos)
+        jint maxAlternatives, jint skipPos, jintArray nextLettersArray, jint nextLettersSize)
 {
     Dictionary *dictionary = (Dictionary*) dict;
     if (dictionary == NULL)
@@ -98,13 +98,16 @@ static int latinime_BinaryDictionary_getSuggestions(
     int *frequencies = env->GetIntArrayElements(frequencyArray, NULL);
     int *inputCodes = env->GetIntArrayElements(inputArray, NULL);
     jchar *outputChars = env->GetCharArrayElements(outputArray, NULL);
+    int *nextLetters = nextLettersArray != NULL ? env->GetIntArrayElements(nextLettersArray, NULL)
+            : NULL;
 
     int count = dictionary->getSuggestions(inputCodes, arraySize, (unsigned short*) outputChars, frequencies,
-            maxWordLength, maxWords, maxAlternatives, skipPos);
+            maxWordLength, maxWords, maxAlternatives, skipPos, nextLetters, nextLettersSize);
     
     env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
     env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
     env->ReleaseCharArrayElements(outputArray, outputChars, 0);
+    env->ReleaseIntArrayElements(nextLettersArray, nextLetters, 0);
     
     return count;
 }
@@ -136,7 +139,7 @@ static JNINativeMethod gMethods[] = {
     {"openNative",           "(Landroid/content/res/AssetManager;Ljava/lang/String;II)I",
                                           (void*)latinime_BinaryDictionary_open},
     {"closeNative",          "(I)V",            (void*)latinime_BinaryDictionary_close},
-    {"getSuggestionsNative", "(I[II[C[IIIII)I",  (void*)latinime_BinaryDictionary_getSuggestions},
+    {"getSuggestionsNative", "(I[II[C[IIIII[II)I",  (void*)latinime_BinaryDictionary_getSuggestions},
     {"isValidWordNative",    "(I[CI)Z",         (void*)latinime_BinaryDictionary_isValidWord}
 };
 
diff --git a/dictionary/src/dictionary.cpp b/dictionary/src/dictionary.cpp
index 306aff5270..6e6f441821 100644
--- a/dictionary/src/dictionary.cpp
+++ b/dictionary/src/dictionary.cpp
@@ -49,7 +49,8 @@ Dictionary::~Dictionary()
 }
 
 int Dictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies,
-        int maxWordLength, int maxWords, int maxAlternatives, int skipPos)
+        int maxWordLength, int maxWords, int maxAlternatives, int skipPos,
+        int *nextLetters, int nextLettersSize)
 {
     int suggWords;
     mFrequencies = frequencies;
@@ -61,6 +62,8 @@ int Dictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWor
     mMaxWords = maxWords;
     mSkipPos = skipPos;
     mMaxEditDistance = mInputLength < 5 ? 2 : mInputLength / 2;
+    mNextLettersFrequencies = nextLetters;
+    mNextLettersSize = nextLettersSize;
 
     getWordsRec(0, 0, mInputLength * 3, false, 1, 0, 0);
 
@@ -68,9 +71,27 @@ int Dictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWor
     suggWords = 0;
     while (suggWords < mMaxWords && mFrequencies[suggWords] > 0) suggWords++;
     if (DEBUG_DICT) LOGI("Returning %d words", suggWords);
+
+    if (DEBUG_DICT) {
+        LOGI("Next letters: ");
+        for (int k = 0; k < nextLettersSize; k++) {
+            if (mNextLettersFrequencies[k] > 0) {
+                LOGI("%c = %d,", k, mNextLettersFrequencies[k]);
+            }
+        }
+        LOGI("\n");
+    }
     return suggWords;
 }
 
+void
+Dictionary::registerNextLetter(unsigned short c)
+{
+    if (c < mNextLettersSize) {
+        mNextLettersFrequencies[c]++;
+    }
+}
+
 unsigned short
 Dictionary::getChar(int *pos)
 {
@@ -210,6 +231,9 @@ Dictionary::getWordsRec(int pos, int depth, int maxDepth, bool completion, int s
             mWord[depth] = c;
             if (terminal) {
                 addWord(mWord, depth + 1, freq * snr);
+                if (depth >= mInputLength && mSkipPos < 0) {
+                    registerNextLetter(mWord[mInputLength]);
+                }
             }
             if (childrenAddress != 0) {
                 getWordsRec(childrenAddress, depth + 1, maxDepth,
diff --git a/dictionary/src/dictionary.h b/dictionary/src/dictionary.h
index a12c035c81..3749f3d88b 100644
--- a/dictionary/src/dictionary.h
+++ b/dictionary/src/dictionary.h
@@ -32,7 +32,8 @@ class Dictionary {
 public:
     Dictionary(void *dict, int typedLetterMultipler, int fullWordMultiplier);
     int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies,
-        int maxWordLength, int maxWords, int maxAlternatives, int skipPos);
+            int maxWordLength, int maxWords, int maxAlternatives, int skipPos,
+            int *nextLetters, int nextLettersSize);
     bool isValidWord(unsigned short *word, int length);
     void setAsset(void *asset) { mAsset = asset; }
     void *getAsset() { return mAsset; }
@@ -53,6 +54,7 @@ private:
     void getWordsRec(int pos, int depth, int maxDepth, bool completion, int frequency,
             int inputIndex, int diffs);
     bool isValidWordRec(int pos, unsigned short *word, int offset, int length);
+    void registerNextLetter(unsigned short c);
 
     unsigned char *mDict;
     void *mAsset;
@@ -70,6 +72,8 @@ private:
 
     int mFullWordMultiplier;
     int mTypedLetterMultiplier;
+    int *mNextLettersFrequencies;
+    int mNextLettersSize;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/src/com/android/inputmethod/latin/BinaryDictionary.java b/src/com/android/inputmethod/latin/BinaryDictionary.java
index 68d8b740cd..ec467c88de 100644
--- a/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -65,7 +65,8 @@ public class BinaryDictionary extends Dictionary {
     private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
     private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, 
             char[] outputChars, int[] frequencies,
-            int maxWordLength, int maxWords, int maxAlternatives, int skipPos);
+            int maxWordLength, int maxWords, int maxAlternatives, int skipPos,
+            int[] nextLettersFrequencies, int nextLettersSize);
 
     private final void loadDictionary(Context context, int resId) {
         AssetManager am = context.getResources().getAssets();
@@ -74,7 +75,8 @@ public class BinaryDictionary extends Dictionary {
     }
 
     @Override
-    public void getWords(final WordComposer codes, final WordCallback callback) {
+    public void getWords(final WordComposer codes, final WordCallback callback,
+            int[] nextLettersFrequencies) {
         final int codesSize = codes.size();
         // Wont deal with really long words.
         if (codesSize > MAX_WORD_LENGTH - 1) return;
@@ -90,7 +92,9 @@ public class BinaryDictionary extends Dictionary {
 
         int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize,
                 mOutputChars, mFrequencies,
-                MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES, -1);
+                MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES, -1,
+                nextLettersFrequencies,
+                nextLettersFrequencies != null ? nextLettersFrequencies.length : 0);
 
         // If there aren't sufficient suggestions, search for words by allowing wild cards at
         // the different character positions. This feature is not ready for prime-time as we need
@@ -100,7 +104,8 @@ public class BinaryDictionary extends Dictionary {
             for (int skip = 0; skip < codesSize; skip++) {
                 int tempCount = getSuggestionsNative(mNativeDict, mInputCodes, codesSize,
                         mOutputChars, mFrequencies,
-                        MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES, skip);
+                        MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES, skip,
+                        null, 0);
                 count = Math.max(count, tempCount);
                 if (tempCount > 0) break;
             }
diff --git a/src/com/android/inputmethod/latin/ContactsDictionary.java b/src/com/android/inputmethod/latin/ContactsDictionary.java
index fd7ba55fc5..f53ebf3f5c 100644
--- a/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ b/src/com/android/inputmethod/latin/ContactsDictionary.java
@@ -84,14 +84,15 @@ public class ContactsDictionary extends ExpandableDictionary {
     }
 
     @Override
-    public synchronized void getWords(final WordComposer codes, final WordCallback callback) {
+    public synchronized void getWords(final WordComposer codes, final WordCallback callback,
+            int[] nextLettersFrequencies) {
         synchronized (mUpdatingLock) {
             // If we need to update, start off a background task
             if (mRequiresReload) loadDictionaryAsyncLocked();
             // Currently updating contacts, don't return any results.
             if (mUpdatingContacts) return;
         }
-        super.getWords(codes, callback);
+        super.getWords(codes, callback, nextLettersFrequencies);
     }
 
     @Override
diff --git a/src/com/android/inputmethod/latin/Dictionary.java b/src/com/android/inputmethod/latin/Dictionary.java
index 6c1c856e77..b656d04dc1 100644
--- a/src/com/android/inputmethod/latin/Dictionary.java
+++ b/src/com/android/inputmethod/latin/Dictionary.java
@@ -55,9 +55,14 @@ abstract public class Dictionary {
      * words are added through the callback object.
      * @param composer the key sequence to match
      * @param callback the callback object to send matched words to as possible candidates
+     * @param nextLettersFrequencies array of frequencies of next letters that could follow the
+     *        word so far. For instance, "bracke" can be followed by "t", so array['t'] will have
+     *        a non-zero value on returning from this method. 
+     *        Pass in null if you don't want the dictionary to look up next letters.
      * @see WordCallback#addWord(char[], int, int)
      */
-    abstract public void getWords(final WordComposer composer, final WordCallback callback);
+    abstract public void getWords(final WordComposer composer, final WordCallback callback,
+            int[] nextLettersFrequencies);
 
     /**
      * Checks if the given word occurs in the dictionary
diff --git a/src/com/android/inputmethod/latin/ExpandableDictionary.java b/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 1589168ee4..648f577caf 100644
--- a/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -27,6 +27,7 @@ public class ExpandableDictionary extends Dictionary {
     private char[] mWordBuilder = new char[MAX_WORD_LENGTH];
     private int mMaxDepth;
     private int mInputLength;
+    private int[] mNextLettersFrequencies;
 
     public static final int MAX_WORD_LENGTH = 32;
     private static final char QUOTE = '\'';
@@ -116,8 +117,10 @@ public class ExpandableDictionary extends Dictionary {
     }
 
     @Override
-    public void getWords(final WordComposer codes, final WordCallback callback) {
+    public void getWords(final WordComposer codes, final WordCallback callback,
+            int[] nextLettersFrequencies) {
         mInputLength = codes.size();
+        mNextLettersFrequencies = nextLettersFrequencies;
         if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
         // Cache the codes so that we don't have to lookup an array list
         for (int i = 0; i < mInputLength; i++) {
@@ -216,6 +219,11 @@ public class ExpandableDictionary extends Dictionary {
                     if (!callback.addWord(word, 0, depth + 1, freq * snr)) {
                         return;
                     }
+                    // Add to frequency of next letters for predictive correction
+                    if (mNextLettersFrequencies != null && depth >= inputIndex && skipPos < 0
+                            && mNextLettersFrequencies.length > word[inputIndex]) {
+                        mNextLettersFrequencies[word[inputIndex]]++;
+                    }
                 }
                 if (children != null) {
                     getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
diff --git a/src/com/android/inputmethod/latin/LatinIME.java b/src/com/android/inputmethod/latin/LatinIME.java
index 56971a5342..470b0048e4 100644
--- a/src/com/android/inputmethod/latin/LatinIME.java
+++ b/src/com/android/inputmethod/latin/LatinIME.java
@@ -1340,6 +1340,8 @@ public class LatinIME extends InputMethodService
     private void updateSuggestions() {
         mSuggestionShouldReplaceCurrentWord = false;
 
+        ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(null);
+
         // Check if we have a suggestion engine attached.
         if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
             return;
@@ -1351,6 +1353,10 @@ public class LatinIME extends InputMethodService
         }
 
         List<CharSequence> stringList = mSuggest.getSuggestions(mInputView, mWord, false);
+        int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
+
+        ((LatinKeyboard) mInputView.getKeyboard()).setPreferredLetters(nextLettersFrequencies);
+
         boolean correctionAvailable = mSuggest.hasMinimalCorrection();
         //|| mCorrectionMode == mSuggest.CORRECTION_FULL;
         CharSequence typedWord = mWord.getTypedWord();
diff --git a/src/com/android/inputmethod/latin/LatinKeyboard.java b/src/com/android/inputmethod/latin/LatinKeyboard.java
index 64b4529f35..9b742a5f99 100644
--- a/src/com/android/inputmethod/latin/LatinKeyboard.java
+++ b/src/com/android/inputmethod/latin/LatinKeyboard.java
@@ -35,11 +35,15 @@ import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.inputmethodservice.Keyboard;
 import android.text.TextPaint;
+import android.util.Log;
 import android.view.ViewConfiguration;
 import android.view.inputmethod.EditorInfo;
 
 public class LatinKeyboard extends Keyboard {
 
+    private static final boolean DEBUG_PREFERRED_LETTER = false;
+    private static final String TAG = "LatinKeyboard";
+
     private Drawable mShiftLockIcon;
     private Drawable mShiftLockPreviewIcon;
     private Drawable mOldShiftIcon;
@@ -69,6 +73,12 @@ public class LatinKeyboard extends Keyboard {
     private boolean mCurrentlyInSpace;
     private SlidingLocaleDrawable mSlidingLocaleIcon;
     private Rect mBounds = new Rect();
+    private int[] mPrefLetterFrequencies;
+    private boolean mPreemptiveCorrection;
+    private int mPrefLetter;
+    private int mPrefLetterX;
+    private int mPrefLetterY;
+    private int mPrefDistance;
 
     private int mExtensionResId; 
     
@@ -79,6 +89,8 @@ public class LatinKeyboard extends Keyboard {
     private int mShiftState = SHIFT_OFF;
 
     private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f;
+    private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f;
+    private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f;
 
     static int sSpacebarVerticalCorrection;
 
@@ -409,9 +421,18 @@ public class LatinKeyboard extends Keyboard {
         return mCurrentlyInSpace;
     }
 
+    void setPreferredLetters(int[] frequencies) {
+        mPrefLetterFrequencies = frequencies;
+        mPrefLetter = 0;
+    }
+
     void keyReleased() {
         mCurrentlyInSpace = false;
         mSpaceDragLastDiff = 0;
+        mPrefLetter = 0;
+        mPrefLetterX = 0;
+        mPrefLetterY = 0;
+        mPrefDistance = Integer.MAX_VALUE;
         if (mSpaceKey != null) {
             updateLocaleDrag(Integer.MAX_VALUE);
         }
@@ -448,6 +469,79 @@ public class LatinKeyboard extends Keyboard {
                     return insideSpace;
                 }
             }
+        } else if (mPrefLetterFrequencies != null) {
+            // New coordinate? Reset
+            if (mPrefLetterX != x || mPrefLetterY != y) {
+                mPrefLetter = 0;
+                mPrefDistance = Integer.MAX_VALUE;
+            }
+            // Handle preferred next letter
+            final int[] pref = mPrefLetterFrequencies;
+            if (mPrefLetter > 0) {
+                if (DEBUG_PREFERRED_LETTER && mPrefLetter == code
+                        && !key.isInsideSuper(x, y)) {
+                    Log.d(TAG, "CORRECTED !!!!!!");
+                }
+                return mPrefLetter == code;
+            } else {
+                final boolean inside = key.isInsideSuper(x, y);
+                int[] nearby = getNearestKeys(x, y);
+                List<Key> nearbyKeys = getKeys();
+                if (inside) {
+                    // If it's a preferred letter
+                    if (inPrefList(code, pref)) {
+                        // Check if its frequency is much lower than a nearby key
+                        mPrefLetter = code;
+                        mPrefLetterX = x;
+                        mPrefLetterY = y;
+                        for (int i = 0; i < nearby.length; i++) {
+                            Key k = nearbyKeys.get(nearby[i]);
+                            if (k != key && inPrefList(k.codes[0], pref)) {
+                                final int dist = distanceFrom(k, x, y);
+                                if (dist < (int) (k.width * OVERLAP_PERCENTAGE_LOW_PROB) &&
+                                        (pref[k.codes[0]] > pref[mPrefLetter] * 3))  {
+                                    mPrefLetter = k.codes[0];
+                                    mPrefDistance = dist;
+                                    if (DEBUG_PREFERRED_LETTER) {
+                                        Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!");
+                                    }
+                                    break;
+                                }
+                            }
+                        }
+
+                        return mPrefLetter == code;
+                    }
+                }
+
+                // Get the surrounding keys and intersect with the preferred list
+                // For all in the intersection
+                //   if distance from touch point is within a reasonable distance
+                //       make this the pref letter
+                // If no pref letter
+                //   return inside;
+                // else return thiskey == prefletter;
+
+                for (int i = 0; i < nearby.length; i++) {
+                    Key k = nearbyKeys.get(nearby[i]);
+                    if (inPrefList(k.codes[0], pref)) {
+                        final int dist = distanceFrom(k, x, y);
+                        if (dist < (int) (k.width * OVERLAP_PERCENTAGE_HIGH_PROB)
+                                && dist < mPrefDistance)  {
+                            mPrefLetter = k.codes[0];
+                            mPrefLetterX = x;
+                            mPrefLetterY = y;
+                            mPrefDistance = dist;
+                        }
+                    }
+                }
+                // Didn't find any
+                if (mPrefLetter == 0) {
+                    return inside;
+                } else {
+                    return mPrefLetter == code;
+                }
+            }
         }
 
         // Lock into the spacebar
@@ -456,6 +550,19 @@ public class LatinKeyboard extends Keyboard {
         return key.isInsideSuper(x, y);
     }
 
+    private boolean inPrefList(int code, int[] pref) {
+        if (code < pref.length && code >= 0) return pref[code] > 0;
+        return false;
+    }
+
+    private int distanceFrom(Key k, int x, int y) {
+        if (y > k.y && y < k.y + k.height) {
+            return Math.abs(k.x + k.width / 2 - x);
+        } else {
+            return Integer.MAX_VALUE;
+        }
+    }
+
     @Override
     public int[] getNearestKeys(int x, int y) {
         if (mCurrentlyInSpace) {
@@ -512,7 +619,8 @@ public class LatinKeyboard extends Keyboard {
          */
         @Override
         public boolean isInside(int x, int y) {
-            return LatinKeyboard.this.isInside(this, x, y);
+            boolean result = LatinKeyboard.this.isInside(this, x, y);
+            return result;
         }
 
         boolean isInsideSuper(int x, int y) {
diff --git a/src/com/android/inputmethod/latin/LatinKeyboardView.java b/src/com/android/inputmethod/latin/LatinKeyboardView.java
index 05f8aff36f..bdac4a5b52 100644
--- a/src/com/android/inputmethod/latin/LatinKeyboardView.java
+++ b/src/com/android/inputmethod/latin/LatinKeyboardView.java
@@ -20,6 +20,7 @@ import java.util.List;
 
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.inputmethodservice.Keyboard;
 import android.inputmethodservice.KeyboardView;
 import android.inputmethodservice.Keyboard.Key;
@@ -80,6 +81,11 @@ public class LatinKeyboardView extends KeyboardView {
     @Override
     public boolean onTouchEvent(MotionEvent me) {
         LatinKeyboard keyboard = (LatinKeyboard) getKeyboard();
+        if (DEBUG_LINE) {
+            mLastX = (int) me.getX();
+            mLastY = (int) me.getY();
+            invalidate();
+        }
         // Reset any bounding box controls in the keyboard
         if (me.getAction() == MotionEvent.ACTION_DOWN) {
             keyboard.keyReleased();
@@ -203,6 +209,7 @@ public class LatinKeyboardView extends KeyboardView {
     /****************************  INSTRUMENTATION  *******************************/
 
     static final boolean DEBUG_AUTO_PLAY = false;
+    static final boolean DEBUG_LINE = false;
     private static final int MSG_TOUCH_DOWN = 1;
     private static final int MSG_TOUCH_UP = 2;
     
@@ -213,6 +220,9 @@ public class LatinKeyboardView extends KeyboardView {
     private boolean mDownDelivered;
     private Key[] mAsciiKeys = new Key[256];
     private boolean mPlaying;
+    private int mLastX;
+    private int mLastY;
+    private Paint mPaint;
 
     @Override
     public void setKeyboard(Keyboard k) {
@@ -309,5 +319,14 @@ public class LatinKeyboardView extends KeyboardView {
                 mHandler2.sendEmptyMessageDelayed(MSG_TOUCH_DOWN, 20);
             }
         }
+        if (DEBUG_LINE) {
+            if (mPaint == null) {
+                mPaint = new Paint();
+                mPaint.setColor(0x80FFFFFF);
+                mPaint.setAntiAlias(false);
+            }
+            c.drawLine(mLastX, 0, mLastX, getHeight(), mPaint);
+            c.drawLine(0, mLastY, getWidth(), mLastY, mPaint);
+        }
     }
 }
diff --git a/src/com/android/inputmethod/latin/Suggest.java b/src/com/android/inputmethod/latin/Suggest.java
index c3fe996350..5833c02a52 100755
--- a/src/com/android/inputmethod/latin/Suggest.java
+++ b/src/com/android/inputmethod/latin/Suggest.java
@@ -52,6 +52,12 @@ public class Suggest implements Dictionary.WordCallback {
     private int mPrefMaxSuggestions = 12;
 
     private int[] mPriorities = new int[mPrefMaxSuggestions];
+    // Handle predictive correction for only the first 1280 characters for performance reasons
+    // If we support scripts that need latin characters beyond that, we should probably use some
+    // kind of a sparse array or language specific list with a mapping lookup table.
+    // 1280 is the size of the BASE_CHARS array in ExpandableDictionary, which is a basic set of
+    // latin characters.
+    private int[] mNextLettersFrequencies = new int[1280];
     private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
     private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
     private boolean mHaveCorrection;
@@ -162,7 +168,8 @@ public class Suggest implements Dictionary.WordCallback {
         mCapitalize = wordComposer.isCapitalized();
         collectGarbage();
         Arrays.fill(mPriorities, 0);
-        
+        Arrays.fill(mNextLettersFrequencies, 0);
+
         // Save a lowercase version of the original word
         mOriginalWord = wordComposer.getTypedWord();
         if (mOriginalWord != null) {
@@ -175,17 +182,17 @@ public class Suggest implements Dictionary.WordCallback {
         if (wordComposer.size() > 1) {
             if (mUserDictionary != null || mContactsDictionary != null) {
                 if (mUserDictionary != null) {
-                    mUserDictionary.getWords(wordComposer, this);
+                    mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
                 }
                 if (mContactsDictionary != null) {
-                    mContactsDictionary.getWords(wordComposer, this);
+                    mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
                 }
 
                 if (mSuggestions.size() > 0 && isValidWord(mOriginalWord)) {
                     mHaveCorrection = true;
                 }
             }
-            mMainDict.getWords(wordComposer, this);
+            mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
             if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 0) {
                 mHaveCorrection = true;
             }
@@ -229,6 +236,10 @@ public class Suggest implements Dictionary.WordCallback {
         return mSuggestions;
     }
 
+    public int[] getNextLettersFrequencies() {
+        return mNextLettersFrequencies;
+    }
+
     private void removeDupes() {
         final ArrayList<CharSequence> suggestions = mSuggestions;
         if (suggestions.size() < 2) return;
diff --git a/src/com/android/inputmethod/latin/UserDictionary.java b/src/com/android/inputmethod/latin/UserDictionary.java
index 2f3447abdc..edd82aaa37 100644
--- a/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/src/com/android/inputmethod/latin/UserDictionary.java
@@ -94,9 +94,10 @@ public class UserDictionary extends ExpandableDictionary {
     }
 
     @Override
-    public synchronized void getWords(final WordComposer codes, final WordCallback callback) {
+    public synchronized void getWords(final WordComposer codes, final WordCallback callback,
+            int[] nextLettersFrequencies) {
         if (mRequiresReload) loadDictionary();
-        super.getWords(codes, callback);
+        super.getWords(codes, callback, nextLettersFrequencies);
     }
 
     @Override
-- 
GitLab