Merge "Create a way to pass the proximity info to the dictionary"
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 713f3ab..9b39e36 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -57,8 +57,6 @@
         public void pickSuggestionManually(int index, CharSequence word);
     }
 
-    private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
-    private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
     // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
     private static final int MAX_SUGGESTIONS = 18;
     private static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -67,8 +65,6 @@
     private static final boolean DBG = LatinImeLogger.sDBG;
 
     private final ViewGroup mCandidatesStrip;
-    private final int mCandidateCountInStrip;
-    private static final int DEFAULT_CANDIDATE_COUNT_IN_STRIP = 3;
     private final ViewGroup mCandidatesPaneControl;
     private final TextView mExpandCandidatesPane;
     private final TextView mCloseCandidatesPane;
@@ -81,15 +77,6 @@
     private final ArrayList<View> mDividers = new ArrayList<View>();
 
     private final int mCandidateStripHeight;
-    private final CharacterStyle mInvertedForegroundColorSpan;
-    private final CharacterStyle mInvertedBackgroundColorSpan;
-    private final int mAutoCorrectHighlight;
-    private static final int AUTO_CORRECT_BOLD = 0x01;
-    private static final int AUTO_CORRECT_UNDERLINE = 0x02;
-    private static final int AUTO_CORRECT_INVERT = 0x04;
-    private final int mColorTypedWord;
-    private final int mColorAutoCorrect;
-    private final int mColorSuggestedCandidate;
 
     private final PopupWindow mPreviewPopup;
     private final TextView mPreviewText;
@@ -102,8 +89,8 @@
     private boolean mShowingAutoCorrectionInverted;
     private boolean mShowingAddToDictionary;
 
-    private final CandidateViewLayoutParams mParams;
-    private static final int PUNCTUATIONS_IN_STRIP = 6;
+    private final SuggestionsStripParams mStripParams;
+    private final SuggestionsPaneParams mPaneParams;
     private static final float MIN_TEXT_XSCALE = 0.75f;
 
     private final UiHandler mHandler = new UiHandler(this);
@@ -157,15 +144,49 @@
         }
     }
 
-    private static class CandidateViewLayoutParams {
-        public final TextPaint mPaint;
+    private static class CandidateViewParams {
         public final int mPadding;
         public final int mDividerWidth;
         public final int mDividerHeight;
         public final int mControlWidth;
+
+        protected CandidateViewParams(TextView word, View divider, View control) {
+            mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
+            divider.measure(WRAP_CONTENT, MATCH_PARENT);
+            mDividerWidth = divider.getMeasuredWidth();
+            mDividerHeight = divider.getMeasuredHeight();
+            mControlWidth = control.getMeasuredWidth();
+        }
+    }
+
+    private static class SuggestionsPaneParams extends CandidateViewParams {
+        public SuggestionsPaneParams(List<TextView> words, List<View> dividers, View control) {
+            super(words.get(0), dividers.get(0), control);
+        }
+    }
+
+    private static class SuggestionsStripParams extends CandidateViewParams {
+        private static final int DEFAULT_CANDIDATE_COUNT_IN_STRIP = 3;
+        private static final int PUNCTUATIONS_IN_STRIP = 6;
+
+        private final int mColorTypedWord;
+        private final int mColorAutoCorrect;
+        private final int mColorSuggestedCandidate;
+        private final int mCandidateCountInStrip;
+
+        private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
+        private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
+        private final CharacterStyle mInvertedForegroundColorSpan;
+        private final CharacterStyle mInvertedBackgroundColorSpan;
+        private static final int AUTO_CORRECT_BOLD = 0x01;
+        private static final int AUTO_CORRECT_UNDERLINE = 0x02;
+        private static final int AUTO_CORRECT_INVERT = 0x04;
+
+        public final TextPaint mPaint;
         private final int mAutoCorrectHighlight;
 
-        public final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+        private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+        private SuggestedWords mSuggestedWords;
 
         public int mCountInStrip;
         // True if the mCountInStrip suggestions can fit in suggestion strip in equally divided
@@ -177,23 +198,114 @@
         public int mVariableWidthForWords;
         public float mScaleX;
 
-        public CandidateViewLayoutParams(Resources res, TextView word, View divider, View control,
-                int autoCorrectHighlight) {
+        public SuggestionsStripParams(Context context, AttributeSet attrs, int defStyle,
+                List<TextView> words, List<View> dividers, View control) {
+            super(words.get(0), dividers.get(0), control);
+            final TypedArray a = context.obtainStyledAttributes(
+                    attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle);
+            mAutoCorrectHighlight = a.getInt(R.styleable.CandidateView_autoCorrectHighlight, 0);
+            mColorTypedWord = a.getColor(R.styleable.CandidateView_colorTypedWord, 0);
+            mColorAutoCorrect = a.getColor(R.styleable.CandidateView_colorAutoCorrect, 0);
+            mColorSuggestedCandidate = a.getColor(R.styleable.CandidateView_colorSuggested, 0);
+            mCandidateCountInStrip = a.getInt(
+                    R.styleable.CandidateView_candidateCountInStrip,
+                    DEFAULT_CANDIDATE_COUNT_IN_STRIP);
+            a.recycle();
+
+            mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
+            mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
+
             mPaint = new TextPaint();
-            final float textSize = res.getDimension(R.dimen.candidate_text_size);
+            final float textSize = context.getResources().getDimension(R.dimen.candidate_text_size);
             mPaint.setTextSize(textSize);
-            mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
-            divider.measure(WRAP_CONTENT, MATCH_PARENT);
-            mDividerWidth = divider.getMeasuredWidth();
-            mDividerHeight = divider.getMeasuredHeight();
-            mControlWidth = control.getMeasuredWidth();
-            mAutoCorrectHighlight = autoCorrectHighlight;
         }
 
-        public void layoutStrip(SuggestedWords suggestions, int maxWidth, int maxCount) {
+        public CharSequence getWord(int pos) {
+            return mTexts.get(pos);
+        }
+
+        public CharSequence getDebugInfo(int pos) {
+            if (DBG) {
+                final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(pos);
+                if (wordInfo != null) {
+                    final CharSequence debugInfo = wordInfo.getDebugString();
+                    if (!TextUtils.isEmpty(debugInfo)) {
+                        return debugInfo;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect) {
+            if (!isAutoCorrect)
+                return word;
+            final int len = word.length();
+            final Spannable spannedWord = new SpannableString(word);
+            if ((mAutoCorrectHighlight & AUTO_CORRECT_BOLD) != 0)
+                spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+            if ((mAutoCorrectHighlight & AUTO_CORRECT_UNDERLINE) != 0)
+                spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+            return spannedWord;
+        }
+
+        public int getWordPosition(int index) {
+            if (index >= 2) {
+                return index;
+            }
+            final boolean willAutoCorrect = !mSuggestedWords.mTypedWordValid
+                    && mSuggestedWords.mHasMinimalSuggestion;
+            return willAutoCorrect ? 1 - index : index;
+        }
+
+        public int getCandidateTextColor(int pos) {
+            final SuggestedWords suggestions = mSuggestedWords;
+            final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
+                    && ((pos == 1 && !suggestions.mTypedWordValid)
+                            || (pos == 0 && suggestions.mTypedWordValid));
+            // TODO: Need to revisit this logic with bigram suggestions
+            final boolean isSuggestedCandidate = (pos != 0);
+            final boolean isPunctuationSuggestions = suggestions.isPunctuationSuggestions();
+
+            final int color;
+            if (isPunctuationSuggestions) {
+                color = mColorTypedWord;
+            } else if (isAutoCorrect) {
+                color = mColorAutoCorrect;
+            } else if (isSuggestedCandidate) {
+                color = mColorSuggestedCandidate;
+            } else {
+                color = mColorTypedWord;
+            }
+            final SuggestedWordInfo info = suggestions.getInfo(pos);
+            if (info != null && info.isPreviousSuggestedWord()) {
+                return applyAlpha(color, 0.5f);
+            } else {
+                return color;
+            }
+        }
+
+        private static int applyAlpha(final int color, final float alpha) {
+            final int newAlpha = (int)(Color.alpha(color) * alpha);
+            return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
+        }
+
+        public CharSequence getInvertedText(CharSequence text) {
+            if ((mAutoCorrectHighlight & AUTO_CORRECT_INVERT) == 0)
+                return null;
+            final int len = text.length();
+            final Spannable word = new SpannableString(text);
+            word.setSpan(mInvertedBackgroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+            word.setSpan(mInvertedForegroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+            return word;
+        }
+
+        public void layoutStrip(SuggestedWords suggestions, int maxWidth) {
+            mSuggestedWords = suggestions;
+            final int maxCount = suggestions.isPunctuationSuggestions()
+                    ? PUNCTUATIONS_IN_STRIP : mCandidateCountInStrip;
             final int size = suggestions.size();
-            if (size == 0) return;
-            setupTexts(suggestions, size, mAutoCorrectHighlight);
+            setupTexts(suggestions, size);
             mCountInStrip = Math.min(maxCount, size);
             mScaleX = 1.0f;
 
@@ -240,25 +352,14 @@
             }
         }
 
-        private void setupTexts(SuggestedWords suggestions, int count, int autoCorrectHighlight) {
+        private void setupTexts(SuggestedWords suggestions, int count) {
             mTexts.clear();
             for (int i = 0; i < count; i++) {
-                final CharSequence suggestion = suggestions.getWord(i);
-                if (suggestion == null) {
-                    // Skip an empty suggestion, but we need to add a place-holder for it in order
-                    // to avoid an exception in the loop in updateSuggestions().
-                    mTexts.add("");
-                    continue;
-                }
-
+                final CharSequence word = suggestions.getWord(i);
                 final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
                         && ((i == 1 && !suggestions.mTypedWordValid)
                                 || (i == 0 && suggestions.mTypedWordValid));
-                // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1
-                // and there are multiple suggestions, such as the default punctuation list.
-                // TODO: Need to revisit this logic with bigram suggestions
-                final CharSequence styled = getStyledCandidateWord(suggestion, isAutoCorrect,
-                        autoCorrectHighlight);
+                final CharSequence styled = getStyledCandidateWord(word, isAutoCorrect);
                 mTexts.add(styled);
             }
         }
@@ -295,16 +396,6 @@
         setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable(
                 context, attrs, defStyle, R.style.CandidateViewStyle));
 
-        final TypedArray a = context.obtainStyledAttributes(
-                attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle);
-        mAutoCorrectHighlight = a.getInt(R.styleable.CandidateView_autoCorrectHighlight, 0);
-        mColorTypedWord = a.getColor(R.styleable.CandidateView_colorTypedWord, 0);
-        mColorAutoCorrect = a.getColor(R.styleable.CandidateView_colorAutoCorrect, 0);
-        mColorSuggestedCandidate = a.getColor(R.styleable.CandidateView_colorSuggested, 0);
-        mCandidateCountInStrip = a.getInt(
-                R.styleable.CandidateView_candidateCountInStrip, DEFAULT_CANDIDATE_COUNT_IN_STRIP);
-        a.recycle();
-
         Resources res = context.getResources();
         LayoutInflater inflater = LayoutInflater.from(context);
         inflater.inflate(R.layout.candidates_strip, this);
@@ -334,9 +425,6 @@
         mWordToSave = (TextView)findViewById(R.id.word_to_save);
         mWordToSave.setOnClickListener(this);
 
-        mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
-        mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
-
         final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
                 attrs, R.styleable.KeyboardView, R.attr.keyboardViewStyle, R.style.KeyboardView);
         final Drawable expandBackground = keyboardViewAttr.getDrawable(
@@ -368,8 +456,9 @@
         });
         mCandidatesPaneControl.measure(WRAP_CONTENT, WRAP_CONTENT);
 
-        mParams = new CandidateViewLayoutParams(res,
-                mWords.get(0), mDividers.get(0), mCandidatesPaneControl, mAutoCorrectHighlight);
+        mStripParams = new SuggestionsStripParams(context, attrs, defStyle,
+                mWords, mDividers, mCandidatesPaneControl);
+        mPaneParams = new SuggestionsPaneParams(mWords, mDividers, mCandidatesPaneControl);
     }
 
     /**
@@ -398,52 +487,20 @@
         }
     }
 
-    private static CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect,
-            int autoCorrectHighlight) {
-        if (!isAutoCorrect)
-            return word;
-        final Spannable spannedWord = new SpannableString(word);
-        if ((autoCorrectHighlight & AUTO_CORRECT_BOLD) != 0)
-            spannedWord.setSpan(BOLD_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-        if ((autoCorrectHighlight & AUTO_CORRECT_UNDERLINE) != 0)
-            spannedWord.setSpan(UNDERLINE_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-        return spannedWord;
-    }
-
-    private int getCandidateTextColor(boolean isAutoCorrect, boolean isSuggestedCandidate,
-            SuggestedWordInfo info) {
-        final int color;
-        if (isAutoCorrect) {
-            color = mColorAutoCorrect;
-        } else if (isSuggestedCandidate) {
-            color = mColorSuggestedCandidate;
-        } else {
-            color = mColorTypedWord;
-        }
-        if (info != null && info.isPreviousSuggestedWord()) {
-            final int newAlpha = (int)(Color.alpha(color) * 0.5f);
-            return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
-        } else {
-            return color;
-        }
-    }
-
     private void updateSuggestions() {
-        final SuggestedWords suggestions = mSuggestions;
-        final List<SuggestedWordInfo> suggestedWordInfoList = suggestions.mSuggestedWordInfoList;
-        final int paneWidth = getWidth();
-        final CandidateViewLayoutParams params = mParams;
-
         clear();
         closeCandidatesPane();
+        final SuggestedWords suggestions = mSuggestions;
         if (suggestions.size() == 0)
             return;
 
-        params.layoutStrip(suggestions, paneWidth, suggestions.isPunctuationSuggestions()
-                ? PUNCTUATIONS_IN_STRIP : mCandidateCountInStrip);
+        final int paneWidth = getWidth();
+        final SuggestionsStripParams stripParams = mStripParams;
+        final SuggestionsPaneParams paneParams = mPaneParams;
+        stripParams.layoutStrip(suggestions, paneWidth);
 
         final int count = Math.min(mWords.size(), suggestions.size());
-        if (count <= params.mCountInStrip && !DBG) {
+        if (count <= stripParams.mCountInStrip && !DBG) {
             mCandidatesPaneControl.setVisibility(GONE);
         } else {
             mCandidatesPaneControl.setVisibility(VISIBLE);
@@ -451,71 +508,53 @@
             mExpandCandidatesPane.setEnabled(true);
         }
 
-        final int countInStrip = params.mCountInStrip;
+        final int countInStrip = stripParams.mCountInStrip;
         View centeringFrom = null, lastView = null;
         int x = 0, y = 0, infoX = 0;
-        for (int i = 0; i < count; i++) {
-            final int pos;
-            if (i <= 1) {
-                final boolean willAutoCorrect = !suggestions.mTypedWordValid
-                        && suggestions.mHasMinimalSuggestion;
-                pos = willAutoCorrect ? 1 - i : i;
-            } else {
-                pos = i;
-            }
-            final CharSequence suggestion = suggestions.getWord(pos);
-            if (suggestion == null) continue;
-
-            final SuggestedWordInfo suggestionInfo = (suggestedWordInfoList != null)
-                    ? suggestedWordInfoList.get(pos) : null;
-            final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
-                    && ((pos == 1 && !suggestions.mTypedWordValid)
-                            || (pos == 0 && suggestions.mTypedWordValid));
-            // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1
-            // and there are multiple suggestions, such as the default punctuation list.
-            // TODO: Need to revisit this logic with bigram suggestions
-            final boolean isSuggestedCandidate = (pos != 0);
-            final boolean isPunctuationSuggestions = (suggestion.length() == 1 && count > 1);
-
+        for (int index = 0; index < count; index++) {
+            final int pos = stripParams.getWordPosition(index);
             final TextView word = mWords.get(pos);
-            final TextPaint paint = word.getPaint();
             final View divider = mDividers.get(pos);
+            final TextPaint paint = word.getPaint();
             // TODO: Reorder candidates in strip as appropriate. The center candidate should hold
             // the word when space is typed (valid typed word or auto corrected word).
-            word.setTextColor(getCandidateTextColor(isAutoCorrect,
-                    isSuggestedCandidate || isPunctuationSuggestions, suggestionInfo));
-            final CharSequence styled = params.mTexts.get(pos);
+            word.setTextColor(stripParams.getCandidateTextColor(pos));
+            final CharSequence styled = stripParams.getWord(pos);
 
             final TextView info;
-            if (DBG && suggestionInfo != null
-                    && !TextUtils.isEmpty(suggestionInfo.getDebugString())) {
-                info = mInfos.get(i);
-                info.setText(suggestionInfo.getDebugString());
+            if (DBG) {
+                final CharSequence debugInfo = stripParams.getDebugInfo(index);
+                if (debugInfo != null) {
+                    info = mInfos.get(index);
+                    info.setText(debugInfo);
+                } else {
+                    info = null;
+                }
             } else {
                 info = null;
             }
 
             final CharSequence text;
             final float scaleX;
-            if (i < countInStrip) {
-                if (i == 0 && params.mCountInStrip == 1) {
-                    text = getEllipsizedText(styled, params.mMaxWidth, paint);
+            if (index < countInStrip) {
+                if (index == 0 && stripParams.mCountInStrip == 1) {
+                    text = getEllipsizedText(styled, stripParams.mMaxWidth, paint);
                     scaleX = paint.getTextScaleX();
                 } else {
                     text = styled;
-                    scaleX = params.mScaleX;
+                    scaleX = stripParams.mScaleX;
                 }
                 word.setText(text);
                 word.setTextScaleX(scaleX);
-                if (i != 0) {
+                if (index != 0) {
                     // Add divider if this isn't the left most suggestion in candidate strip.
                     mCandidatesStrip.addView(divider);
                 }
                 mCandidatesStrip.addView(word);
-                if (params.mCanUseFixedWidthColumns) {
+                if (stripParams.mCanUseFixedWidthColumns) {
                     setLayoutWeight(word, 1.0f, mCandidateStripHeight);
                 } else {
-                    final int width = getTextWidth(text, paint) + params.mPadding;
+                    final int width = getTextWidth(text, paint) + stripParams.mPadding;
                     setLayoutWeight(word, width, mCandidateStripHeight);
                 }
                 if (info != null) {
@@ -529,7 +568,7 @@
             } else {
                 paint.setTextScaleX(1.0f);
                 final int textWidth = getTextWidth(styled, paint);
-                int available = paneWidth - x - params.mPadding;
+                int available = paneWidth - x - paneParams.mPadding;
                 if (textWidth >= available) {
                     // Needs new row, centering previous row.
                     centeringCandidates(centeringFrom, lastView, x, paneWidth);
@@ -540,11 +579,11 @@
                     // Add divider if this isn't the left most suggestion in current row.
                     mCandidatesPane.addView(divider);
                     FrameLayoutCompatUtils.placeViewAt(
-                            divider, x, y + (mCandidateStripHeight - params.mDividerHeight) / 2,
-                            params.mDividerWidth, params.mDividerHeight);
-                    x += params.mDividerWidth;
+                            divider, x, y + (mCandidateStripHeight - paneParams.mDividerHeight) / 2,
+                            paneParams.mDividerWidth, paneParams.mDividerHeight);
+                    x += paneParams.mDividerWidth;
                 }
-                available = paneWidth - x - params.mPadding;
+                available = paneWidth - x - paneParams.mPadding;
                 text = getEllipsizedText(styled, available, paint);
                 scaleX = paint.getTextScaleX();
                 word.setText(text);
@@ -670,16 +709,11 @@
     }
 
     public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) {
-        if ((mAutoCorrectHighlight & AUTO_CORRECT_INVERT) == 0)
+        final CharSequence inverted = mStripParams.getInvertedText(autoCorrectedWord);
+        if (inverted == null)
             return;
         final TextView tv = mWords.get(1);
-        final Spannable word = new SpannableString(autoCorrectedWord);
-        final int wordLength = word.length();
-        word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
-                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-        word.setSpan(mInvertedForegroundColorSpan, 0, wordLength,
-                Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-        tv.setText(word);
+        tv.setText(inverted);
         mShowingAutoCorrectionInverted = true;
     }
 
@@ -724,7 +758,7 @@
             return;
 
         final TextView previewText = mPreviewText;
-        previewText.setTextColor(mColorTypedWord);
+        previewText.setTextColor(mStripParams.mColorTypedWord);
         previewText.setText(word);
         previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index add1835..6c91c45 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -115,7 +115,7 @@
 
     private static final int SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY = 2;
     private static final int ACCUMULATE_START_INPUT_VIEW_DELAY = 20;
-    private static final int RESTORE_KEYBOARD_STATE_DELAY = 300;
+    private static final int RESTORE_KEYBOARD_STATE_DELAY = 500;
 
     /**
      * The name of the scheme used by the Package Manager to warn of a new package installation,
diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
deleted file mode 100644
index eb740e1..0000000
--- a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-
-import java.util.List;
-import java.util.Locale;
-
-class PrivateBinaryDictionaryGetter {
-    private PrivateBinaryDictionaryGetter() {}
-    public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context) {
-        return null;
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index b77cbd1..c1c46fa 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -31,7 +31,7 @@
     public final boolean mTypedWordValid;
     public final boolean mHasMinimalSuggestion;
     public final boolean mIsPunctuationSuggestions;
-    public final List<SuggestedWordInfo> mSuggestedWordInfoList;
+    private final List<SuggestedWordInfo> mSuggestedWordInfoList;
 
     private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
             boolean hasMinimalSuggestion, boolean isPunctuationSuggestions,
@@ -55,6 +55,10 @@
         return mWords.get(pos);
     }
 
+    public SuggestedWordInfo getInfo(int pos) {
+        return mSuggestedWordInfoList != null ? mSuggestedWordInfoList.get(pos) : null;
+    }
+
     public boolean hasAutoCorrectionWord() {
         return mHasMinimalSuggestion && size() > 1 && !mTypedWordValid;
     }
diff --git a/native/src/correction_state.cpp b/native/src/correction_state.cpp
index add6cf6..b2c77b0 100644
--- a/native/src/correction_state.cpp
+++ b/native/src/correction_state.cpp
@@ -58,32 +58,49 @@
     return CorrectionState::RankingAlgorithm::calcFreqForSplitTwoWords(firstFreq, secondFreq, this);
 }
 
-int CorrectionState::getFinalFreq(const int inputIndex, const int outputIndex, const int freq) {
-    const bool sameLength = (mExcessivePos == mInputLength - 1) ? (mInputLength == inputIndex + 2)
-            : (mInputLength == inputIndex + 1);
-    const int matchCount = mMatchedCharCount;
+int CorrectionState::getFinalFreq(const unsigned short *word, const int freq) {
+    if (mProximityInfo->sameAsTyped(word, mOutputIndex + 1) || mOutputIndex < MIN_SUGGEST_DEPTH) {
+        return -1;
+    }
+    const bool sameLength = (mExcessivePos == mInputLength - 1) ? (mInputLength == mInputIndex + 2)
+            : (mInputLength == mInputIndex + 1);
     return CorrectionState::RankingAlgorithm::calculateFinalFreq(
-            inputIndex, outputIndex, matchCount, freq, sameLength, this);
+            mInputIndex, mOutputIndex, mMatchedCharCount, freq, sameLength, this);
 }
 
-void CorrectionState::initDepth() {
-    mMatchedCharCount = 0;
+void CorrectionState::initProcessState(
+        const int matchCount, const int inputIndex, const int outputIndex) {
+    mMatchedCharCount = matchCount;
+    mInputIndex = inputIndex;
+    mOutputIndex = outputIndex;
+}
+
+void CorrectionState::getProcessState(int *matchedCount, int *inputIndex, int *outputIndex) {
+    *matchedCount = mMatchedCharCount;
+    *inputIndex = mInputIndex;
+    *outputIndex = mOutputIndex;
 }
 
 void CorrectionState::charMatched() {
     ++mMatchedCharCount;
 }
 
-void CorrectionState::goUpTree(const int matchCount) {
-    mMatchedCharCount = matchCount;
+// TODO: remove
+int CorrectionState::getOutputIndex() {
+    return mOutputIndex;
 }
 
-void CorrectionState::slideTree(const int matchCount) {
-    mMatchedCharCount = matchCount;
+// TODO: remove
+int CorrectionState::getInputIndex() {
+    return mInputIndex;
 }
 
-void CorrectionState::goDownTree(int *matchedCount) {
-    *matchedCount = mMatchedCharCount;
+void CorrectionState::incrementInputIndex() {
+    ++mInputIndex;
+}
+
+void CorrectionState::incrementOutputIndex() {
+    ++mOutputIndex;
 }
 
 CorrectionState::~CorrectionState() {
diff --git a/native/src/correction_state.h b/native/src/correction_state.h
index 7bbad5f..cc3c3e6 100644
--- a/native/src/correction_state.h
+++ b/native/src/correction_state.h
@@ -28,16 +28,25 @@
 class CorrectionState {
 
 public:
+    typedef enum {
+        ALLOW_ALL,
+        UNRELATED,
+        RELATED
+    } CorrectionStateType;
+
     CorrectionState(const int typedLetterMultiplier, const int fullWordMultiplier);
     void initCorrectionState(const ProximityInfo *pi, const int inputLength);
     void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
             const int spaceProximityPos, const int missingSpacePos);
-    void initDepth();
     void checkState();
-    void goUpTree(const int matchCount);
-    void slideTree(const int matchCount);
-    void goDownTree(int *matchedCount);
+    void initProcessState(const int matchCount, const int inputIndex, const int outputIndex);
+    void getProcessState(int *matchedCount, int *inputIndex, int *outputIndex);
     void charMatched();
+    void incrementInputIndex();
+    void incrementOutputIndex();
+    int getOutputIndex();
+    int getInputIndex();
+
     virtual ~CorrectionState();
     int getSkipPos() const {
         return mSkipPos;
@@ -55,7 +64,7 @@
         return mMissingSpacePos;
     }
     int getFreqForSplitTwoWords(const int firstFreq, const int secondFreq);
-    int getFinalFreq(const int inputIndex, const int outputIndex, const int freq);
+    int getFinalFreq(const unsigned short *word, const int freq);
 
 private:
 
@@ -71,6 +80,8 @@
     int mMissingSpacePos;
 
     int mMatchedCharCount;
+    int mInputIndex;
+    int mOutputIndex;
 
     class RankingAlgorithm {
     public:
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index f5648d3..b95da99 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -363,27 +363,25 @@
     mStackSiblingPos[0] = rootPosition;
     mStackOutputIndex[0] = 0;
     mStackMatchedCount[0] = 0;
-    mCorrectionState->initDepth();
 
     // Depth first search
     while (depth >= 0) {
         if (mStackChildCount[depth] > 0) {
             --mStackChildCount[depth];
             bool traverseAllNodes = mStackTraverseAll[depth];
-            int inputIndex = mStackInputIndex[depth];
             int diffs = mStackDiffs[depth];
             int siblingPos = mStackSiblingPos[depth];
-            int outputIndex = mStackOutputIndex[depth];
             int firstChildPos;
-            mCorrectionState->slideTree(mStackMatchedCount[depth]);
+            mCorrectionState->initProcessState(
+                    mStackMatchedCount[depth], mStackInputIndex[depth], mStackOutputIndex[depth]);
 
             // depth will never be greater than maxDepth because in that case,
             // needsToTraverseChildrenNodes should be false
-            const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, outputIndex,
-                    maxDepth, traverseAllNodes, inputIndex, diffs,
+            const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos,
+                    maxDepth, traverseAllNodes, diffs,
                     mCorrectionState, &childCount,
-                    &firstChildPos, &traverseAllNodes, &inputIndex, &diffs,
-                    &siblingPos, &outputIndex);
+                    &firstChildPos, &traverseAllNodes, &diffs,
+                    &siblingPos);
             // Update next sibling pos
             mStackSiblingPos[depth] = siblingPos;
             if (needsToTraverseChildrenNodes) {
@@ -391,21 +389,15 @@
                 ++depth;
                 mStackChildCount[depth] = childCount;
                 mStackTraverseAll[depth] = traverseAllNodes;
-                mStackInputIndex[depth] = inputIndex;
                 mStackDiffs[depth] = diffs;
                 mStackSiblingPos[depth] = firstChildPos;
-                mStackOutputIndex[depth] = outputIndex;
 
-                int matchedCount;
-                mCorrectionState->goDownTree(&matchedCount);
-                mStackMatchedCount[depth] = matchedCount;
-            } else {
-                mCorrectionState->slideTree(mStackMatchedCount[depth]);
+                mCorrectionState->getProcessState(&mStackMatchedCount[depth],
+                        &mStackInputIndex[depth], &mStackOutputIndex[depth]);
             }
         } else {
             // Goes to parent sibling node
             --depth;
-            mCorrectionState->goUpTree(mStackMatchedCount[depth]);
         }
     }
 }
@@ -446,13 +438,11 @@
 }
 
 
-inline void UnigramDictionary::onTerminal(unsigned short int* word, const int outputIndex,
-        const int inputIndex, const int freq, CorrectionState *correctionState) {
-    if (!mProximityInfo->sameAsTyped(word, outputIndex + 1) && outputIndex >= MIN_SUGGEST_DEPTH) {
-        const int finalFreq = correctionState->getFinalFreq(inputIndex, outputIndex, freq);
-        if (finalFreq >= 0) {
-            addWord(word, outputIndex + 1, finalFreq);
-        }
+inline void UnigramDictionary::onTerminal(
+        unsigned short int* word, const int freq, CorrectionState *correctionState) {
+    const int finalFreq = correctionState->getFinalFreq(word, freq);
+    if (finalFreq >= 0) {
+        addWord(word, correctionState->getOutputIndex() + 1, finalFreq);
     }
 }
 
@@ -667,12 +657,10 @@
 // there aren't any more nodes at this level, it merely returns the address of the first byte after
 // the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any
 // given level, as output into newCount when traversing this level's parent.
-inline bool UnigramDictionary::processCurrentNode(const int initialPos, const int initialOutputPos,
-        const int maxDepth, const bool initialTraverseAllNodes, int inputIndex,
-        const int initialDiffs,
+inline bool UnigramDictionary::processCurrentNode(const int initialPos, const int maxDepth,
+        const bool initialTraverseAllNodes, const int initialDiffs,
         CorrectionState *correctionState, int *newCount, int *newChildrenPosition,
-        bool *newTraverseAllNodes, int *newInputIndex, int *newDiffs,
-        int *nextSiblingPosition, int *newOutputIndex) {
+        bool *newTraverseAllNodes, int *newDiffs, int *nextSiblingPosition) {
     const int skipPos = correctionState->getSkipPos();
     const int excessivePos = correctionState->getExcessivePos();
     const int transposedPos = correctionState->getTransposedPos();
@@ -680,9 +668,9 @@
         correctionState->checkState();
     }
     int pos = initialPos;
-    int internalOutputPos = initialOutputPos;
     int traverseAllNodes = initialTraverseAllNodes;
     int diffs = initialDiffs;
+    const int initialInputIndex = correctionState->getInputIndex();
 
     // Flags contain the following information:
     // - Address type (MASK_GROUP_ADDRESS_TYPE) on two bits:
@@ -726,16 +714,18 @@
 
         // This has to be done for each virtual char (this forwards the "inputIndex" which
         // is the index in the user-inputted chars, as read by proximity chars.
-        if (excessivePos == internalOutputPos && inputIndex < mInputLength - 1) {
-            ++inputIndex;
+        if (excessivePos == correctionState->getOutputIndex()
+                && correctionState->getInputIndex() < mInputLength - 1) {
+            correctionState->incrementInputIndex();
         }
-        if (traverseAllNodes || needsToSkipCurrentNode(c, inputIndex, skipPos, internalOutputPos)) {
-            mWord[internalOutputPos] = c;
+        if (traverseAllNodes || needsToSkipCurrentNode(
+                c, correctionState->getInputIndex(), skipPos, correctionState->getOutputIndex())) {
+            mWord[correctionState->getOutputIndex()] = c;
             if (traverseAllNodes && isTerminal) {
                 // The frequency should be here, because we come here only if this is actually
                 // a terminal node, and we are on its last char.
                 const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
-                onTerminal(mWord, internalOutputPos, inputIndex, freq, mCorrectionState);
+                onTerminal(mWord, freq, mCorrectionState);
             }
             if (!hasChildren) {
                 // If we don't have children here, that means we finished processing all
@@ -750,11 +740,15 @@
                 return false;
             }
         } else {
-            int inputIndexForProximity = inputIndex;
+            int inputIndexForProximity = correctionState->getInputIndex();
 
             if (transposedPos >= 0) {
-                if (inputIndex == transposedPos) ++inputIndexForProximity;
-                if (inputIndex == (transposedPos + 1)) --inputIndexForProximity;
+                if (correctionState->getInputIndex() == transposedPos) {
+                    ++inputIndexForProximity;
+                }
+                if (correctionState->getInputIndex() == (transposedPos + 1)) {
+                    --inputIndexForProximity;
+                }
             }
 
             int matchedProximityCharId = mProximityInfo->getMatchedProximityId(
@@ -775,18 +769,31 @@
                         BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
                 return false;
             }
-            mWord[internalOutputPos] = c;
+            mWord[correctionState->getOutputIndex()] = c;
             // If inputIndex is greater than mInputLength, that means there is no
             // proximity chars. So, we don't need to check proximity.
             if (ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) {
                 correctionState->charMatched();
             }
-            const bool isSameAsUserTypedLength = mInputLength == inputIndex + 1
-                    || (excessivePos == mInputLength - 1 && inputIndex == mInputLength - 2);
+            const bool isSameAsUserTypedLength = mInputLength
+                    == correctionState->getInputIndex() + 1
+                            || (excessivePos == mInputLength - 1
+                                        && correctionState->getInputIndex() == mInputLength - 2);
             if (isSameAsUserTypedLength && isTerminal) {
                 const int freq = BinaryFormat::readFrequencyWithoutMovingPointer(DICT_ROOT, pos);
-                onTerminal(mWord, internalOutputPos, inputIndex, freq, mCorrectionState);
+                onTerminal(mWord, freq, mCorrectionState);
             }
+            // Start traversing all nodes after the index exceeds the user typed length
+            traverseAllNodes = isSameAsUserTypedLength;
+            diffs = diffs
+                    + ((ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) ? 1 : 0);
+            // Finally, we are ready to go to the next character, the next "virtual node".
+            // We should advance the input index.
+            // We do this in this branch of the 'if traverseAllNodes' because we are still matching
+            // characters to input; the other branch is not matching them but searching for
+            // completions, this is why it does not have to do it.
+            correctionState->incrementInputIndex();
+
             // This character matched the typed character (enough to traverse the node at least)
             // so we just evaluated it. Now we should evaluate this virtual node's children - that
             // is, if it has any. If it has no children, we're done here - so we skip the end of
@@ -799,19 +806,10 @@
                         BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
                 return false;
             }
-            // Start traversing all nodes after the index exceeds the user typed length
-            traverseAllNodes = isSameAsUserTypedLength;
-            diffs = diffs
-                    + ((ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) ? 1 : 0);
-            // Finally, we are ready to go to the next character, the next "virtual node".
-            // We should advance the input index.
-            // We do this in this branch of the 'if traverseAllNodes' because we are still matching
-            // characters to input; the other branch is not matching them but searching for
-            // completions, this is why it does not have to do it.
-            ++inputIndex;
         }
         // Optimization: Prune out words that are too long compared to how much was typed.
-        if (internalOutputPos >= maxDepth || diffs > mMaxEditDistance) {
+        if (isTerminal
+                && (correctionState->getOutputIndex() >= maxDepth || diffs > mMaxEditDistance)) {
             // We are giving up parsing this node and its children. Skip the rest of the node,
             // output the sibling position, and return that we don't want to traverse children.
             if (!isLastChar) {
@@ -822,18 +820,18 @@
                     BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
             return false;
         }
+        // Also, the next char is one "virtual node" depth more than this char.
+        correctionState->incrementOutputIndex();
 
         // Prepare for the next character. Promote the prefetched char to current char - the loop
         // will take care of prefetching the next. If we finally found our last char, nextc will
         // contain NOT_A_CHARACTER.
         c = nextc;
-        // Also, the next char is one "virtual node" depth more than this char.
-        ++internalOutputPos;
     } while (NOT_A_CHARACTER != c);
 
     // If inputIndex is greater than mInputLength, that means there are no proximity chars.
     // Here, that's all we are interested in so we don't need to check for isSameAsUserTypedLength.
-    if (mInputLength <= *newInputIndex) {
+    if (mInputLength <= initialInputIndex) {
         traverseAllNodes = true;
     }
 
@@ -841,8 +839,6 @@
     // variables. Output them to the caller.
     *newTraverseAllNodes = traverseAllNodes;
     *newDiffs = diffs;
-    *newInputIndex = inputIndex;
-    *newOutputIndex = internalOutputPos;
 
     // Now we finished processing this node, and we want to traverse children. If there are no
     // children, we can't come here.
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index c67eaf6..cb86da4 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -94,18 +94,14 @@
             const int inputLength, const int missingSpacePos, CorrectionState *correctionState);
     void getMistypedSpaceWords(
             const int inputLength, const int spaceProximityPos, CorrectionState *correctionState);
-    void onTerminal(unsigned short int* word, const int depth,
-            const int inputIndex, const int freq,
-            CorrectionState *correctionState);
+    void onTerminal(unsigned short int* word, const int freq, CorrectionState *correctionState);
     bool needsToSkipCurrentNode(const unsigned short c,
             const int inputIndex, const int skipPos, const int depth);
     // Process a node by considering proximity, missing and excessive character
-    bool processCurrentNode(const int initialPos, const int initialDepth,
-            const int maxDepth, const bool initialTraverseAllNodes, int inputIndex,
-            const int initialDiffs,
+    bool processCurrentNode(const int initialPos, const int maxDepth,
+            const bool initialTraverseAllNodes, const int initialDiffs,
             CorrectionState *correctionState, int *newCount, int *newChildPosition,
-            bool *newTraverseAllNodes, int *newInputIndex, int *newDiffs,
-            int *nextSiblingPosition, int *nextOutputIndex);
+            bool *newTraverseAllNodes, int *newDiffs, int *nextSiblingPosition);
     int getMostFrequentWordLike(const int startInputIndex, const int inputLength,
             unsigned short *word);
     int getMostFrequentWordLikeInner(const uint16_t* const inWord, const int length,