Merge "Support invalidating multiple keys in KeyboardView"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 219e327..8df51d6 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -139,7 +139,7 @@
             <enum name="alwaysDisplay" value="-1" />
         </attr>
         <attr name="delayBeforeFadeoutLangageOnSpacebar" format="integer" />
-        <attr name="finalFadeoutFactorOfLanguageOnSpacebar" format="float" />
+        <attr name="finalAlphaOfLanguageOnSpacebar" format="integer" />
         <!-- Key detection hysteresis distance. -->
         <attr name="keyHysteresisDistance" format="dimension" />
         <!-- Touch noise threshold time in millisecond -->
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 4bc4ddb..eaca642 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -80,7 +80,7 @@
         <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
         <item name="durationOfFadeoutLanguageOnSpacebar">200</item>
         <item name="delayBeforeFadeoutLangageOnSpacebar">1200</item>
-        <item name="finalFadeoutFactorOfLanguageOnSpacebar">0.5</item>
+        <item name="finalAlphaOfLanguageOnSpacebar">128</item>
     </style>
     <style
         name="LatinKeyboardView"
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 5346712..b7f1ddd 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -154,7 +154,7 @@
         mIconsSet = params.mIconsSet;
         mAdditionalProximityChars = params.mAdditionalProximityChars;
 
-        mProximityInfo = new ProximityInfo(
+        mProximityInfo = new ProximityInfo(params.mId.mLocale.toString(),
                 params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
                 mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection,
                 params.mAdditionalProximityChars);
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 9168d07..f4e766c 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -21,7 +21,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Align;
 import android.graphics.Typeface;
@@ -77,13 +76,14 @@
     private Drawable mSpaceIcon;
     // Stuff to draw language name on spacebar.
     private ValueAnimator mLanguageOnSpacebarAnimator;
-    private float mFinalFadeoutFactorOfLanguageOnSpacebar;
+    private int mFinalAlphaOfLanguageOnSpacebar;
     private int mDurationOfFadeoutLanguageOnSpacebar;
+    private static final int ALPHA_OPAQUE = 255;
     private static final int LANGUAGE_ON_SPACEBAR_NEVER_DISPLAY = 0;
     private static final int LANGUAGE_ON_SPACEBAR_ALWAYS_DISPLAY = -1;
     private boolean mNeedsToDisplayLanguage;
     private Locale mSpacebarLocale;
-    private float mSpacebarTextFadeFactor = 0.0f;
+    private int mSpacebarTextAlpha;
     private final float mSpacebarTextRatio;
     private float mSpacebarTextSize;
     private final int mSpacebarTextColor;
@@ -344,8 +344,8 @@
                 LANGUAGE_ON_SPACEBAR_NEVER_DISPLAY);
         final int delayBeforeFadeoutLanguageOnSpacebar = a.getInt(
                 R.styleable.LatinKeyboardView_delayBeforeFadeoutLangageOnSpacebar, 0);
-        mFinalFadeoutFactorOfLanguageOnSpacebar = a.getFloat(
-                R.styleable.LatinKeyboardView_finalFadeoutFactorOfLanguageOnSpacebar, 0.0f);
+        mFinalAlphaOfLanguageOnSpacebar = a.getInt(
+                R.styleable.LatinKeyboardView_finalAlphaOfLanguageOnSpacebar, 0);
 
         final KeyTimerParams keyTimerParams = new KeyTimerParams(a);
         mPointerTrackerParams = new PointerTrackerParams(a);
@@ -361,8 +361,8 @@
 
         PointerTracker.setParameters(mPointerTrackerParams);
 
-        mLanguageOnSpacebarAnimator = ValueAnimator.ofFloat(
-                1.0f, mFinalFadeoutFactorOfLanguageOnSpacebar);
+        mLanguageOnSpacebarAnimator = ValueAnimator.ofInt(
+                ALPHA_OPAQUE, mFinalAlphaOfLanguageOnSpacebar);
         mLanguageOnSpacebarAnimator.setStartDelay(delayBeforeFadeoutLanguageOnSpacebar);
         if (mDurationOfFadeoutLanguageOnSpacebar > 0) {
             mLanguageOnSpacebarAnimator.setDuration(mDurationOfFadeoutLanguageOnSpacebar);
@@ -370,7 +370,7 @@
         mLanguageOnSpacebarAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
-                mSpacebarTextFadeFactor = (Float)animation.getAnimatedValue();
+                mSpacebarTextAlpha = (Integer)animation.getAnimatedValue();
                 invalidateKey(mSpaceKey);
             }
         });
@@ -794,15 +794,15 @@
         mLanguageOnSpacebarAnimator.cancel();
         mNeedsToDisplayLanguage = needsToDisplayLanguage;
         if (mDurationOfFadeoutLanguageOnSpacebar == LANGUAGE_ON_SPACEBAR_NEVER_DISPLAY) {
-            mSpacebarTextFadeFactor = 0.0f;
+            mNeedsToDisplayLanguage = false;
         } else if (mDurationOfFadeoutLanguageOnSpacebar == LANGUAGE_ON_SPACEBAR_ALWAYS_DISPLAY) {
-            mSpacebarTextFadeFactor = 1.0f;
+            mSpacebarTextAlpha = ALPHA_OPAQUE;
         } else {
             if (subtypeChanged && needsToDisplayLanguage) {
-                mSpacebarTextFadeFactor = 1.0f;
+                mSpacebarTextAlpha = ALPHA_OPAQUE;
                 mLanguageOnSpacebarAnimator.start();
             } else {
-                mSpacebarTextFadeFactor = mFinalFadeoutFactorOfLanguageOnSpacebar;
+                mSpacebarTextAlpha = mFinalAlphaOfLanguageOnSpacebar;
             }
         }
         invalidateKey(mSpaceKey);
@@ -835,12 +835,6 @@
         }
     }
 
-    private static int getSpacebarTextColor(int color, float fadeFactor) {
-        final int newColor = Color.argb((int)(Color.alpha(color) * fadeFactor),
-                Color.red(color), Color.green(color), Color.blue(color));
-        return newColor;
-    }
-
     // Compute width of text with specified text size using paint.
     private int getTextWidth(Paint paint, String text, float textSize) {
         paint.setTextSize(textSize);
@@ -888,7 +882,7 @@
         final int width = key.mWidth;
         final int height = key.mHeight;
 
-        // If application locales are explicitly selected.
+        // If input subtypes are explicitly selected.
         if (mNeedsToDisplayLanguage) {
             final String language = layoutLanguageOnSpacebar(paint, mSpacebarLocale, width,
                     mSpacebarTextSize);
@@ -898,9 +892,11 @@
             final float descent = paint.descent();
             final float textHeight = -paint.ascent() + descent;
             final float baseline = height / 2 + textHeight / 2;
-            paint.setColor(getSpacebarTextColor(mSpacebarTextShadowColor, mSpacebarTextFadeFactor));
+            paint.setColor(mSpacebarTextShadowColor);
+            paint.setAlpha(mSpacebarTextAlpha);
             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
-            paint.setColor(getSpacebarTextColor(mSpacebarTextColor, mSpacebarTextFadeFactor));
+            paint.setColor(mSpacebarTextColor);
+            paint.setAlpha(mSpacebarTextAlpha);
             canvas.drawText(language, width / 2, baseline - descent, paint);
         }
 
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 1480bba..8a65a5f 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.keyboard;
 
 import android.graphics.Rect;
+import android.text.TextUtils;
 
 import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
 import com.android.inputmethod.latin.JniUtils;
@@ -46,10 +47,17 @@
     private final int mKeyboardHeight;
     private final int mMostCommonKeyWidth;
     private final Key[][] mGridNeighbors;
+    private final String mLocaleStr;
 
-    ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int mostCommonKeyWidth,
+    ProximityInfo(String localeStr, int gridWidth, int gridHeight, int minWidth, int height,
+            int mostCommonKeyWidth,
             int mostCommonKeyHeight, Set<Key> keys, TouchPositionCorrection touchPositionCorrection,
             Map<Integer, List<Integer>> additionalProximityChars) {
+        if (TextUtils.isEmpty(localeStr)) {
+            mLocaleStr = "";
+        } else {
+            mLocaleStr = localeStr;
+        }
         mGridWidth = gridWidth;
         mGridHeight = gridHeight;
         mGridSize = mGridWidth * mGridHeight;
@@ -69,14 +77,14 @@
     }
 
     public static ProximityInfo createDummyProximityInfo() {
-        return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key> emptySet(),
+        return new ProximityInfo("", 1, 1, 1, 1, 1, 1, Collections.<Key> emptySet(),
                 null, Collections.<Integer, List<Integer>> emptyMap());
     }
 
     public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity) {
         final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
         spellCheckerProximityInfo.mNativeProximityInfo =
-                spellCheckerProximityInfo.setProximityInfoNative(
+                spellCheckerProximityInfo.setProximityInfoNative("",
                         SpellCheckerProximityInfo.ROW_SIZE, 480, 300, 11, 3, (480 / 10), proximity,
                         0, null, null, null, null, null, null, null, null);
         return spellCheckerProximityInfo;
@@ -87,7 +95,8 @@
         JniUtils.loadNativeLibrary();
     }
 
-    private native long setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
+    private native long setProximityInfoNative(
+            String locale, int maxProximityCharsSize, int displayWidth,
             int displayHeight, int gridWidth, int gridHeight,
             int mostCommonKeyWidth, int[] proximityCharsArray,
             int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
@@ -154,7 +163,7 @@
             calculateSweetSpotParams = false;
         }
 
-        mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE,
+        mNativeProximityInfo = setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE,
                 keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth,
                 proximityCharsArray,
                 keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index dc5bd2e..2bbda78 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -72,6 +72,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
@@ -925,8 +926,9 @@
             final List<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
                     SuggestedWords.Builder.getFromApplicationSpecifiedCompletions(
                             applicationSpecifiedCompletions);
-            SuggestedWords.Builder builder = new SuggestedWords.Builder()
-                    .addWords(applicationSuggestedWords)
+            SuggestedWords.Builder builder = new SuggestedWords.Builder(applicationSuggestedWords,
+                    false /* allowsToBeAutoCorrected */,
+                    false /* isPunctuationSuggestions */)
                     .setTypedWordValid(false)
                     .setHasMinimalSuggestion(false);
             // When in fullscreen mode, show completions generated by the application
@@ -1782,8 +1784,16 @@
             if (previousSuggestions == mSettingsValues.mSuggestPuncList) {
                 previousSuggestions = SuggestedWords.EMPTY;
             }
-            final SuggestedWords.Builder obsoleteSuggestionsBuilder = new SuggestedWords.Builder()
-                    .addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+            final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
+                    SuggestedWords.Builder.getTypedWordAndPreviousSuggestions(
+                            typedWord, previousSuggestions);
+            final SuggestedWords.Builder obsoleteSuggestionsBuilder =
+                    new SuggestedWords.Builder(typedWordAndPreviousSuggestions,
+                            false /* allowsToBeAutoCorrected */,
+                            false /* isPunctuationSuggestions */)
+                            .setTypedWordValid(false)
+                            .setHasMinimalSuggestion(false);
+
             showSuggestions(obsoleteSuggestionsBuilder.build(), typedWord);
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 7e77020..c5198b3 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -183,9 +183,9 @@
                         KeySpecParser.getLabel(puncSpec)));
             }
         }
-        final SuggestedWords.Builder builder = new SuggestedWords.Builder()
-                .addWords(puncList)
-                .setIsPunctuationSuggestions();
+        final SuggestedWords.Builder builder = new SuggestedWords.Builder(puncList,
+                false /* allowsToBeAutoCorrected */,
+                true /* isPunctuationSuggestions */);
         return builder.build();
     }
 
@@ -203,9 +203,9 @@
                 }
             }
         }
-        final SuggestedWords.Builder builder = new SuggestedWords.Builder()
-                .addWords(puncOutputTextList)
-                .setIsPunctuationSuggestions();
+        final SuggestedWords.Builder builder = new SuggestedWords.Builder(puncOutputTextList,
+                false /* allowsToBeAutoCorrected */,
+                true /* isPunctuationSuggestions */);
         return builder.build();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/StringBuilderPool.java b/java/src/com/android/inputmethod/latin/StringBuilderPool.java
deleted file mode 100644
index a663ed4..0000000
--- a/java/src/com/android/inputmethod/latin/StringBuilderPool.java
+++ /dev/null
@@ -1,70 +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 java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A pool of string builders to be used from anywhere.
- */
-public class StringBuilderPool {
-    // Singleton
-    private static final StringBuilderPool sInstance = new StringBuilderPool();
-    private static final boolean DEBUG = false;
-    private StringBuilderPool() {}
-    // TODO: Make this a normal array with a size of 20, or a ConcurrentQueue
-    private final List<StringBuilder> mPool =
-            Collections.synchronizedList(new ArrayList<StringBuilder>());
-
-    public static StringBuilder getStringBuilder(final int initialSize) {
-        // TODO: although the pool is synchronized, the following is not thread-safe.
-        // Two threads entering this at the same time could take the same size of the pool and the
-        // second to attempt removing this index from the pool would crash with an
-        // IndexOutOfBoundsException.
-        // At the moment this pool is only used in Suggest.java and only in one thread so it's
-        // okay. The simplest thing to do here is probably to replace the ArrayList with a
-        // ConcurrentQueue.
-        final int poolSize = sInstance.mPool.size();
-        final StringBuilder sb = poolSize > 0 ? (StringBuilder) sInstance.mPool.remove(poolSize - 1)
-                : new StringBuilder(initialSize);
-        sb.setLength(0);
-        return sb;
-    }
-
-    public static void recycle(final StringBuilder garbage) {
-        if (DEBUG) {
-            final int gid = garbage.hashCode();
-            for (final StringBuilder q : sInstance.mPool) {
-                if (gid == q.hashCode()) throw new RuntimeException("Duplicate id " + gid);
-            }
-        }
-        sInstance.mPool.add(garbage);
-    }
-
-    public static void ensureCapacity(final int capacity, final int initialSize) {
-        for (int i = sInstance.mPool.size(); i < capacity; ++i) {
-            final StringBuilder sb = new StringBuilder(initialSize);
-            sInstance.mPool.add(sb);
-        }
-    }
-
-    public static int getSize() {
-        return sInstance.mPool.size();
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 81c3b4e..7b34cae 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -142,7 +142,7 @@
             for (int j = 0; j < i; j++) {
                 CharSequence previous = suggestions.get(j);
                 if (TextUtils.equals(cur, previous)) {
-                    removeFromSuggestions(suggestions, i);
+                    suggestions.remove(i);
                     i--;
                     break;
                 }
@@ -151,14 +151,6 @@
         }
     }
 
-    private static void removeFromSuggestions(final ArrayList<CharSequence> suggestions,
-            final int index) {
-        final CharSequence garbage = suggestions.remove(index);
-        if (garbage instanceof StringBuilder) {
-            StringBuilderPool.recycle((StringBuilder)garbage);
-        }
-    }
-
     public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
         if (returnsNameInThisLocale) {
             return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index e04a4e8..28cbc97 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -98,7 +98,7 @@
     private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
 
     private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
-    ArrayList<CharSequence> mBigramSuggestions  = new ArrayList<CharSequence>();
+    private ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
     private CharSequence mConsideredWord;
 
     // TODO: Remove these member variables by passing more context to addWord() callback method
@@ -122,7 +122,6 @@
     private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
         mWhiteListDictionary = new WhitelistDictionary(context, locale);
         addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
-        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
     }
 
     private void initAsynchronously(final Context context, final int dictionaryResId,
@@ -181,7 +180,7 @@
         return mUnigramDictionaries;
     }
 
-    public int getApproxMaxWordLength() {
+    public static int getApproxMaxWordLength() {
         return APPROX_MAX_WORD_LENGTH;
     }
 
@@ -216,27 +215,11 @@
         mAutoCorrectionThreshold = threshold;
     }
 
-    /**
-     * Number of suggestions to generate from the input key sequence. This has
-     * to be a number between 1 and 100 (inclusive).
-     * @param maxSuggestions
-     * @throws IllegalArgumentException if the number is out of range
-     */
-    public void setMaxSuggestions(int maxSuggestions) {
-        if (maxSuggestions < 1 || maxSuggestions > 100) {
-            throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
-        }
-        mPrefMaxSuggestions = maxSuggestions;
-        mScores = new int[mPrefMaxSuggestions];
-        mBigramScores = new int[PREF_MAX_BIGRAMS];
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
-        StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
-    }
-
-    private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
+    private static CharSequence capitalizeWord(final boolean all, final boolean first,
+            final CharSequence word) {
         if (TextUtils.isEmpty(word) || !(all || first)) return word;
         final int wordLength = word.length();
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         // TODO: Must pay attention to locale when changing case.
         if (all) {
             sb.append(word.toString().toUpperCase());
@@ -250,12 +233,7 @@
     }
 
     protected void addBigramToSuggestions(CharSequence bigram) {
-        // TODO: Try to be a little more shrewd with resource allocation.
-        // At the moment we copy this object because the StringBuilders are pooled (see
-        // StringBuilderPool.java) and when we are finished using mSuggestions and
-        // mBigramSuggestions we will take everything from both and insert them back in the
-        // pool, so we can't allow the same object to be in both lists at the same time.
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         sb.append(bigram);
         mSuggestions.add(sb);
     }
@@ -266,7 +244,7 @@
         mIsFirstCharCapitalized = false;
         mIsAllUpperCase = false;
         mTrailingSingleQuotesCount = 0;
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
+        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
         Arrays.fill(mScores, 0);
 
         // Treating USER_TYPED as UNIGRAM suggestion for logging now.
@@ -274,7 +252,7 @@
         mConsideredWord = "";
 
         Arrays.fill(mBigramScores, 0);
-        collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
+        mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
 
         CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
         if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
@@ -291,10 +269,10 @@
 
         StringUtils.removeDupes(mSuggestions);
 
-        return new SuggestedWords.Builder()
-                .addWords(SuggestedWords.Builder.getFromCharSequenceList(mSuggestions))
-                .setAllowsToBeAutoCorrected(false)
-                .setHasAutoCorrection(false);
+        return new SuggestedWords.Builder(
+                SuggestedWords.Builder.getFromCharSequenceList(mSuggestions),
+                false /* allowsToBeAutoCorrected */,
+                false /* isPunctuationSuggestions */);
     }
 
     // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
@@ -305,7 +283,7 @@
         mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
         mIsAllUpperCase = wordComposer.isAllUpperCase();
         mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
-        collectGarbage(mSuggestions, mPrefMaxSuggestions);
+        mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
         Arrays.fill(mScores, 0);
 
         final String typedWord = wordComposer.getTypedWord();
@@ -328,7 +306,7 @@
         if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
             // At first character typed, search only the bigrams
             Arrays.fill(mBigramScores, 0);
-            collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
+            mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
 
             if (!TextUtils.isEmpty(prevWordForBigram)) {
                 CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
@@ -413,6 +391,7 @@
         StringUtils.removeDupes(mSuggestions);
 
         final SuggestedWords.Builder builder;
+        final ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList;
         if (DBG) {
             // TODO: this doesn't take into account the fact that removing dupes from mSuggestions
             // may have made mScores[] and mSuggestions out of sync.
@@ -421,8 +400,7 @@
             double normalizedScore = BinaryDictionary.calcNormalizedScore(
                     typedWord, autoCorrectionSuggestion.toString(),
                     autoCorrectionSuggestionScore);
-            ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
-                    new ArrayList<SuggestedWords.SuggestedWordInfo>();
+            scoreInfoList = new ArrayList<SuggestedWords.SuggestedWordInfo>();
             scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(autoCorrectionSuggestion, "+",
                     false));
             final int suggestionsSize = mSuggestions.size();
@@ -446,14 +424,8 @@
                 scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(mSuggestions.get(i),
                         "--", false));
             }
-            builder = new SuggestedWords.Builder().addWords(scoreInfoList)
-                    .setAllowsToBeAutoCorrected(allowsToBeAutoCorrected)
-                    .setHasAutoCorrection(hasAutoCorrection);
         } else {
-            builder = new SuggestedWords.Builder()
-                    .addWords(SuggestedWords.Builder.getFromCharSequenceList(mSuggestions))
-                    .setAllowsToBeAutoCorrected(allowsToBeAutoCorrected)
-                    .setHasAutoCorrection(hasAutoCorrection);
+            scoreInfoList = SuggestedWords.Builder.getFromCharSequenceList(mSuggestions);
         }
 
         boolean autoCorrectionAvailable = hasAutoCorrection;
@@ -463,6 +435,8 @@
         }
         // Don't auto-correct words with multiple capital letter
         autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
+        builder = new SuggestedWords.Builder(scoreInfoList, allowsToBeAutoCorrected,
+                false /* isPunctuationSuggestions */);
         builder.setTypedWordValid(!allowsToBeAutoCorrected).setHasMinimalSuggestion(
                 autoCorrectionAvailable);
         if (allowsToBeAutoCorrected && builder.size() > 1 && mAutoCorrectionThreshold > 0
@@ -542,7 +516,7 @@
 
         System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
         sortedScores[pos] = score;
-        final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+        final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
         // TODO: Must pay attention to locale when changing case.
         if (mIsAllUpperCase) {
             sb.append(new String(word, offset, length).toUpperCase());
@@ -559,10 +533,7 @@
         }
         suggestions.add(pos, sb);
         if (suggestions.size() > prefMaxSuggestions) {
-            final CharSequence garbage = suggestions.remove(prefMaxSuggestions);
-            if (garbage instanceof StringBuilder) {
-                StringBuilderPool.recycle((StringBuilder)garbage);
-            }
+            suggestions.remove(prefMaxSuggestions);
         } else {
             LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
         }
@@ -573,40 +544,22 @@
         // TODO This is almost O(n^2). Might need fix.
         // search whether the word appeared in bigram data
         int bigramSuggestSize = mBigramSuggestions.size();
-        for(int i = 0; i < bigramSuggestSize; i++) {
-            if(mBigramSuggestions.get(i).length() == length) {
+        for (int i = 0; i < bigramSuggestSize; i++) {
+            if (mBigramSuggestions.get(i).length() == length) {
                 boolean chk = true;
-                for(int j = 0; j < length; j++) {
-                    if(mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
+                for (int j = 0; j < length; j++) {
+                    if (mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
                         chk = false;
                         break;
                     }
                 }
-                if(chk) return i;
+                if (chk) return i;
             }
         }
 
         return -1;
     }
 
-    private static void collectGarbage(ArrayList<CharSequence> suggestions,
-            int prefMaxSuggestions) {
-        int poolSize = StringBuilderPool.getSize();
-        int garbageSize = suggestions.size();
-        while (poolSize < prefMaxSuggestions && garbageSize > 0) {
-            final CharSequence garbage = suggestions.get(garbageSize - 1);
-            if (garbage instanceof StringBuilder) {
-                StringBuilderPool.recycle((StringBuilder)garbage);
-                poolSize++;
-            }
-            garbageSize--;
-        }
-        if (poolSize == prefMaxSuggestions + 1) {
-            Log.w("Suggest", "String pool got too big: " + poolSize);
-        }
-        suggestions.clear();
-    }
-
     public void close() {
         final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
         dictionaries.addAll(mUnigramDictionaries.values());
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 7dd85f6..f62e99c 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.latin;
 
-import android.text.TextUtils;
 import android.view.inputmethod.CompletionInfo;
 
 import java.util.ArrayList;
@@ -76,42 +75,20 @@
     public static class Builder {
         private boolean mTypedWordValid;
         private boolean mHasMinimalSuggestion;
-        private boolean mIsPunctuationSuggestions;
+        private final boolean mIsPunctuationSuggestions;
         private boolean mShouldBlockAutoCorrectionBySafetyNet;
-        private boolean mAllowsToBeAutoCorrected;
-        private boolean mHasAutoCorrection;
-        private List<SuggestedWordInfo> mSuggestedWordInfoList =
-                new ArrayList<SuggestedWordInfo>();
+        private final boolean mAllowsToBeAutoCorrected;
+        private final List<SuggestedWordInfo> mSuggestedWordInfoList;
 
-        public Builder() {
-            // Nothing to do here.
+        public Builder(final List<SuggestedWordInfo> suggestedWordInfoList,
+                final boolean allowsToBeAutoCorrected,
+                final boolean isPunctuationSuggestions) {
+            mSuggestedWordInfoList = suggestedWordInfoList;
+            mAllowsToBeAutoCorrected = allowsToBeAutoCorrected;
+            mIsPunctuationSuggestions = isPunctuationSuggestions;
         }
 
-        // TODO: the following method is a wrapper to satisfy tests. Update tests and remove it.
-        public Builder addWords(final List<CharSequence> words,
-                final List<SuggestedWordInfo> suggestedWordInfoList) {
-            return addWords(suggestedWordInfoList);
-        }
-
-        public Builder addWords(List<SuggestedWordInfo> suggestedWordInfoList) {
-            final int N = suggestedWordInfoList.size();
-            for (int i = 0; i < N; ++i) {
-                SuggestedWordInfo suggestedWordInfo = suggestedWordInfoList.get(i);
-                addWord(suggestedWordInfo.mWord, suggestedWordInfo);
-            }
-            return this;
-        }
-
-        /* package for tests */
-        Builder addWord(CharSequence word, SuggestedWordInfo suggestedWordInfo) {
-            if (!TextUtils.isEmpty(suggestedWordInfo.mWord)) {
-                // It's okay if suggestedWordInfo is null since it's checked where it's used.
-                mSuggestedWordInfoList.add(suggestedWordInfo);
-            }
-            return this;
-        }
-
-        public static List<SuggestedWordInfo> getFromCharSequenceList(
+        public static ArrayList<SuggestedWordInfo> getFromCharSequenceList(
                 final List<CharSequence> wordList) {
             final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
             for (CharSequence word : wordList) {
@@ -139,46 +116,29 @@
             return this;
         }
 
-        public Builder setIsPunctuationSuggestions() {
-            mIsPunctuationSuggestions = true;
-            return this;
-        }
-
         public Builder setShouldBlockAutoCorrectionBySafetyNet() {
             mShouldBlockAutoCorrectionBySafetyNet = true;
             return this;
         }
 
-        public Builder setAllowsToBeAutoCorrected(final boolean allowsToBeAutoCorrected) {
-            mAllowsToBeAutoCorrected = allowsToBeAutoCorrected;
-            return this;
-        }
-
-        public Builder setHasAutoCorrection(final boolean hasAutoCorrection) {
-            mHasAutoCorrection = hasAutoCorrection;
-            return this;
-        }
-
         // Should get rid of the first one (what the user typed previously) from suggestions
         // and replace it with what the user currently typed.
-        public Builder addTypedWordAndPreviousSuggestions(CharSequence typedWord,
-                SuggestedWords previousSuggestions) {
-            mSuggestedWordInfoList.clear();
+        public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
+                final CharSequence typedWord, final SuggestedWords previousSuggestions) {
+            final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
             final HashSet<String> alreadySeen = new HashSet<String>();
-            addWord(typedWord, new SuggestedWordInfo(typedWord, null, false));
+            suggestionsList.add(new SuggestedWordInfo(typedWord, null, false));
             alreadySeen.add(typedWord.toString());
             final int previousSize = previousSuggestions.size();
             for (int pos = 1; pos < previousSize; pos++) {
                 final String prevWord = previousSuggestions.getWord(pos).toString();
                 // Filter out duplicate suggestion.
                 if (!alreadySeen.contains(prevWord)) {
-                    addWord(prevWord, new SuggestedWordInfo(prevWord, null, true));
+                    suggestionsList.add(new SuggestedWordInfo(prevWord, null, true));
                     alreadySeen.add(prevWord);
                 }
             }
-            mTypedWordValid = false;
-            mHasMinimalSuggestion = false;
-            return this;
+            return suggestionsList;
         }
 
         public SuggestedWords build() {
@@ -199,10 +159,6 @@
             return mAllowsToBeAutoCorrected;
         }
 
-        public boolean hasAutoCorrection() {
-            return mHasAutoCorrection;
-        }
-
         @Override
         public String toString() {
             // Pretty-print method to help debug
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 7e5953b..ffaede3 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -30,6 +30,7 @@
     jni_common.cpp
 
 LATIN_IME_CORE_SRC_FILES := \
+    additional_proximity_chars.cpp \
     basechars.cpp \
     bigram_dictionary.cpp \
     char_utils.cpp \
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index 844b230..e47a64a 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -25,16 +25,19 @@
 #include <assert.h>
 #include <errno.h>
 #include <stdio.h>
+#include <string>
 
 namespace latinime {
 
 static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
-        jint maxProximityCharsSize, jint displayWidth, jint displayHeight, jint gridWidth,
-        jint gridHeight, jint mostCommonkeyWidth, jintArray proximityCharsArray, jint keyCount,
-        jintArray keyXCoordinateArray, jintArray keyYCoordinateArray, jintArray keyWidthArray,
-        jintArray keyHeightArray, jintArray keyCharCodeArray,
+        jstring localejStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
+        jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityCharsArray,
+        jint keyCount, jintArray keyXCoordinateArray, jintArray keyYCoordinateArray,
+        jintArray keyWidthArray, jintArray keyHeightArray, jintArray keyCharCodeArray,
         jfloatArray sweetSpotCenterXArray, jfloatArray sweetSpotCenterYArray,
         jfloatArray sweetSpotRadiusArray) {
+    const char *localeStrPtr = env->GetStringUTFChars(localejStr, 0);
+    const std::string localeStr(localeStrPtr);
     jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, 0);
     jint *keyXCoordinates = safeGetIntArrayElements(env, keyXCoordinateArray);
     jint *keyYCoordinates = safeGetIntArrayElements(env, keyYCoordinateArray);
@@ -44,7 +47,8 @@
     jfloat *sweetSpotCenterXs = safeGetFloatArrayElements(env, sweetSpotCenterXArray);
     jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
     jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
-    ProximityInfo *proximityInfo = new ProximityInfo(maxProximityCharsSize, displayWidth,
+    ProximityInfo *proximityInfo = new ProximityInfo(
+            localeStr, maxProximityCharsSize, displayWidth,
             displayHeight, gridWidth, gridHeight, mostCommonkeyWidth,
             (const uint32_t*)proximityChars,
             keyCount, (const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
@@ -60,6 +64,7 @@
     safeReleaseIntArrayElements(env, keyYCoordinateArray, keyYCoordinates);
     safeReleaseIntArrayElements(env, keyXCoordinateArray, keyXCoordinates);
     env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
+    env->ReleaseStringUTFChars(localejStr, localeStrPtr);
     return (jlong)proximityInfo;
 }
 
@@ -70,7 +75,7 @@
 }
 
 static JNINativeMethod sKeyboardMethods[] = {
-    {"setProximityInfoNative", "(IIIIII[II[I[I[I[I[I[F[F[F)J",
+    {"setProximityInfoNative", "(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J",
             (void*)latinime_Keyboard_setProximityInfo},
     {"releaseProximityInfoNative", "(J)V", (void*)latinime_Keyboard_release}
 };
diff --git a/native/src/additional_proximity_chars.cpp b/native/src/additional_proximity_chars.cpp
new file mode 100644
index 0000000..401c85a
--- /dev/null
+++ b/native/src/additional_proximity_chars.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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 "additional_proximity_chars.h"
+
+namespace latinime {
+const std::string AdditionalProximityChars::LOCALE_EN_US("en");
+
+const uint32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
+    'e', 'i', 'o', 'u'
+};
+
+const uint32_t AdditionalProximityChars::EN_US_ADDITIONAL_E[EN_US_ADDITIONAL_E_SIZE] = {
+    'a', 'i', 'o', 'u'
+};
+
+const uint32_t AdditionalProximityChars::EN_US_ADDITIONAL_I[EN_US_ADDITIONAL_I_SIZE] = {
+    'a', 'e', 'o', 'u'
+};
+
+const uint32_t AdditionalProximityChars::EN_US_ADDITIONAL_O[EN_US_ADDITIONAL_O_SIZE] = {
+    'a', 'e', 'i', 'u'
+};
+
+const uint32_t AdditionalProximityChars::EN_US_ADDITIONAL_U[EN_US_ADDITIONAL_U_SIZE] = {
+    'a', 'e', 'i', 'o'
+};
+}
diff --git a/native/src/additional_proximity_chars.h b/native/src/additional_proximity_chars.h
new file mode 100644
index 0000000..e0049d9
--- /dev/null
+++ b/native/src/additional_proximity_chars.h
@@ -0,0 +1,92 @@
+/*
+ * 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_ADDITIONAL_PROXIMITY_CHARS_H
+#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
+
+#include <stdint.h>
+#include <string>
+
+namespace latinime {
+
+class AdditionalProximityChars {
+ private:
+    static const std::string LOCALE_EN_US;
+    static const int EN_US_ADDITIONAL_A_SIZE = 4;
+    static const uint32_t EN_US_ADDITIONAL_A[];
+    static const int EN_US_ADDITIONAL_E_SIZE = 4;
+    static const uint32_t EN_US_ADDITIONAL_E[];
+    static const int EN_US_ADDITIONAL_I_SIZE = 4;
+    static const uint32_t EN_US_ADDITIONAL_I[];
+    static const int EN_US_ADDITIONAL_O_SIZE = 4;
+    static const uint32_t EN_US_ADDITIONAL_O[];
+    static const int EN_US_ADDITIONAL_U_SIZE = 4;
+    static const uint32_t EN_US_ADDITIONAL_U[];
+
+    static bool isEnLocale(const std::string* locale_str) {
+        return locale_str && locale_str->size() >= LOCALE_EN_US.size()
+                && locale_str->compare(0, LOCALE_EN_US.size(), LOCALE_EN_US);
+    }
+
+ public:
+    static int getAdditionalCharsSize(const std::string* locale_str, const uint16_t c) {
+        if (!isEnLocale(locale_str)) {
+            return 0;
+        }
+        switch(c) {
+        case 'a':
+            return EN_US_ADDITIONAL_A_SIZE;
+        case 'e':
+            return EN_US_ADDITIONAL_E_SIZE;
+        case 'i':
+            return EN_US_ADDITIONAL_I_SIZE;
+        case 'o':
+            return EN_US_ADDITIONAL_O_SIZE;
+        case 'u':
+            return EN_US_ADDITIONAL_U_SIZE;
+        default:
+            return 0;
+        }
+    }
+
+    static const uint32_t* getAdditionalChars(const std::string* locale_str, const uint32_t c) {
+        if (!isEnLocale(locale_str)) {
+            return 0;
+        }
+        switch(c) {
+        case 'a':
+            return EN_US_ADDITIONAL_A;
+        case 'e':
+            return EN_US_ADDITIONAL_E;
+        case 'i':
+            return EN_US_ADDITIONAL_I;
+        case 'o':
+            return EN_US_ADDITIONAL_O;
+        case 'u':
+            return EN_US_ADDITIONAL_U;
+        default:
+            return 0;
+        }
+    }
+
+    static bool hasAdditionalChars(const std::string* locale_str, const uint32_t c) {
+        return getAdditionalCharsSize(locale_str, c) > 0;
+    }
+};
+
+}
+
+#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
index ad19f58..e590740 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -16,10 +16,11 @@
 
 #include <assert.h>
 #include <stdio.h>
-#include <string.h>
+#include <string>
 
 #define LOG_TAG "LatinIME: proximity_info.cpp"
 
+#include "additional_proximity_chars.h"
 #include "dictionary.h"
 #include "proximity_info.h"
 
@@ -33,9 +34,9 @@
     }
 }
 
-ProximityInfo::ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,
-        const int keyboardHeight, const int gridWidth, const int gridHeight,
-        const int mostCommonKeyWidth,
+ProximityInfo::ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
+        const int keyboardWidth, const int keyboardHeight, const int gridWidth,
+        const int gridHeight, const int mostCommonKeyWidth,
         const uint32_t *proximityCharsArray, const int keyCount, const int32_t *keyXCoordinates,
         const int32_t *keyYCoordinates, const int32_t *keyWidths, const int32_t *keyHeights,
         const int32_t *keyCharCodes, const float *sweetSpotCenterXs, const float *sweetSpotCenterYs,
@@ -49,6 +50,7 @@
           HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
                   && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
                   && sweetSpotCenterYs && sweetSpotRadii),
+          mLocaleStr(localeStr),
           mInputXCoordinates(0), mInputYCoordinates(0),
           mTouchPositionCorrectionEnabled(false) {
     const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
@@ -163,6 +165,30 @@
             }
         }
     }
+    const int existingProximitySize = insertPos;
+    for (int i = 0; i < existingProximitySize; ++i) {
+        const uint32_t c = inputCodes[i];
+        const int additionalProximitySize =
+                AdditionalProximityChars::hasAdditionalChars(&mLocaleStr, c);
+        if (additionalProximitySize <= 0) {
+            continue;
+        }
+        const uint32_t* additionalProximityChars =
+                AdditionalProximityChars::getAdditionalChars(&mLocaleStr, c);
+        for (int j = 0; j < additionalProximitySize; ++j) {
+            const uint32_t ac = additionalProximityChars[j];
+            int k = 0;
+            for (; k < insertPos; ++k) {
+                if ((int)ac == inputCodes[k]) {
+                    break;
+                }
+            }
+            if (k < insertPos) {
+                continue;
+            }
+            inputCodes[insertPos++] = ac;
+        }
+    }
     // TODO: calculate additional chars
 }
 
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index caabadf..8c342e0 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -18,6 +18,7 @@
 #define LATINIME_PROXIMITY_INFO_H
 
 #include <stdint.h>
+#include <string>
 
 #include "defines.h"
 
@@ -43,9 +44,9 @@
         ADDITIONAL_PROXIMITY_CHAR
     } ProximityType;
 
-    ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,
-            const int keybaordHeight, const int gridWidth, const int gridHeight,
-            const int mostCommonkeyWidth,
+    ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
+            const int keyboardWidth, const int keybaordHeight, const int gridWidth,
+            const int gridHeight, const int mostCommonkeyWidth,
             const uint32_t *proximityCharsArray, const int keyCount, const int32_t *keyXCoordinates,
             const int32_t *keyYCoordinates, const int32_t *keyWidths, const int32_t *keyHeights,
             const int32_t *keyCharCodes, const float *sweetSpotCenterXs,
@@ -103,6 +104,7 @@
     const int CELL_HEIGHT;
     const int KEY_COUNT;
     const bool HAS_TOUCH_POSITION_CORRECTION_DATA;
+    const std::string mLocaleStr;
     const int *mInputCodes;
     const int *mInputXCoordinates;
     const int *mInputYCoordinates;