diff --git a/java/res/values-en/additional-proximitychars.xml b/java/res/values-en/additional-proximitychars.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0e127679610f2b4384b83c6ecaf13ae6d167e058
--- /dev/null
+++ b/java/res/values-en/additional-proximitychars.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <string-array name="additional_proximitychars">
+        <!-- Empty entry terminates the proximity chars array. -->
+
+        <!-- Additional proximity chars for a -->
+        <item>a</item>
+        <item>e</item>
+        <item>i</item>
+        <item>o</item>
+        <item>u</item>
+        <item></item>
+        <!-- Additional proximity chars for e -->
+        <item>e</item>
+        <item>a</item>
+        <item>i</item>
+        <item>o</item>
+        <item>u</item>
+        <item></item>
+        <!-- Additional proximity chars for i -->
+        <item>i</item>
+        <item>a</item>
+        <item>e</item>
+        <item>o</item>
+        <item>u</item>
+        <item></item>
+        <!-- Additional proximity chars for o -->
+        <item>o</item>
+        <item>a</item>
+        <item>e</item>
+        <item>i</item>
+        <item>u</item>
+        <item></item>
+        <!-- Additional proximity chars for u -->
+        <item>u</item>
+        <item>a</item>
+        <item>e</item>
+        <item>i</item>
+        <item>o</item>
+        <item></item>
+    </string-array>
+
+</resources>
\ No newline at end of file
diff --git a/java/res/values/additional-proximitychars.xml b/java/res/values/additional-proximitychars.xml
new file mode 100644
index 0000000000000000000000000000000000000000..03d10d5d8cfb500b1160d9c5f26bc5aab3fe0281
--- /dev/null
+++ b/java/res/values/additional-proximitychars.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string-array name="additional_proximitychars">
+    </string-array>
+</resources>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 0d271625b52c8e6917204fca0104bf7893c3ad5a..bff491ffd6221bef0f3c14a9c40d2c84c23b63f1 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -19,12 +19,14 @@ package com.android.inputmethod.keyboard;
 import android.util.Log;
 
 import java.util.Arrays;
+import java.util.List;
 
 public class KeyDetector {
     private static final String TAG = KeyDetector.class.getSimpleName();
     private static final boolean DEBUG = false;
 
     public static final int NOT_A_CODE = -1;
+    private static final int ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE = 2;
 
     private final int mKeyHysteresisDistanceSquared;
 
@@ -154,8 +156,9 @@ public class KeyDetector {
         return distances.length;
     }
 
-    private void getNearbyKeyCodes(final int[] allCodes) {
+    private void getNearbyKeyCodes(final int primaryCode, final int[] allCodes) {
         final Key[] neighborKeys = mNeighborKeys;
+        final int maxCodesSize = allCodes.length;
 
         // allCodes[0] should always have the key code even if it is a non-letter key.
         if (neighborKeys[0] == null) {
@@ -164,7 +167,7 @@ public class KeyDetector {
         }
 
         int numCodes = 0;
-        for (int j = 0; j < neighborKeys.length && numCodes < allCodes.length; j++) {
+        for (int j = 0; j < neighborKeys.length && numCodes < maxCodesSize; j++) {
             final Key key = neighborKeys[j];
             if (key == null)
                 break;
@@ -174,6 +177,38 @@ public class KeyDetector {
                 continue;
             allCodes[numCodes++] = code;
         }
+        if (maxCodesSize <= numCodes) {
+            return;
+        }
+        if (primaryCode != NOT_A_CODE) {
+            final List<Integer> additionalChars =
+                    mKeyboard.getAdditionalProximityChars().get(primaryCode);
+            if (additionalChars == null || additionalChars.size() == 0) {
+                return;
+            }
+            int currentCodesSize = numCodes;
+            allCodes[numCodes++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
+            if (maxCodesSize <= numCodes) {
+                return;
+            }
+            // TODO: This is O(N^2). Assuming additionalChars.size() is up to 4 or 5.
+            for (int i = 0; i < additionalChars.size(); ++i) {
+                final int additionalChar = additionalChars.get(i);
+                boolean contains = false;
+                for (int j = 0; j < currentCodesSize; ++j) {
+                    if (additionalChar == allCodes[j]) {
+                        contains = true;
+                        break;
+                    }
+                }
+                if (!contains) {
+                    allCodes[numCodes++] = additionalChar;
+                    if (maxCodesSize <= numCodes) {
+                        return;
+                    }
+                }
+            }
+        }
     }
 
     /**
@@ -205,7 +240,7 @@ public class KeyDetector {
         }
 
         if (allCodes != null && allCodes.length > 0) {
-            getNearbyKeyCodes(allCodes);
+            getNearbyKeyCodes(primaryKey != null ? primaryKey.mCode : NOT_A_CODE, allCodes);
             if (DEBUG) {
                 Log.d(TAG, "x=" + x + " y=" + y
                         + " primary=" + printableCode(primaryKey)
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 6653dec4b4c7580c6a829bcf7e1d6da57ff3b1b5..10e0a5b015e5569630448ebc1b3d0ef9fb592a3a 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -20,6 +20,7 @@ import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -38,10 +39,12 @@ import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -130,6 +133,8 @@ public class Keyboard {
 
     private final ProximityInfo mProximityInfo;
 
+    public final Map<Integer, List<Integer>> mAdditionalProximityChars;
+
     public Keyboard(Params params) {
         mId = params.mId;
         mThemeId = params.mThemeId;
@@ -146,10 +151,12 @@ public class Keyboard {
         mKeys = Collections.unmodifiableSet(params.mKeys);
         mShiftKeys = Collections.unmodifiableSet(params.mShiftKeys);
         mIconsSet = params.mIconsSet;
+        mAdditionalProximityChars = params.mAdditionalProximityChars;
 
         mProximityInfo = new ProximityInfo(
                 params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
-                mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection);
+                mMostCommonKeyWidth, mMostCommonKeyHeight, mKeys, params.mTouchPositionCorrection,
+                params.mAdditionalProximityChars);
     }
 
     public ProximityInfo getProximityInfo() {
@@ -227,6 +234,9 @@ public class Keyboard {
         public final Set<Key> mKeys = new HashSet<Key>();
         public final Set<Key> mShiftKeys = new HashSet<Key>();
         public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+        // TODO: Should be in Key instead of Keyboard.Params?
+        public final Map<Integer, List<Integer>> mAdditionalProximityChars =
+                new HashMap<Integer, List<Integer>>();
 
         public KeyboardSet.KeysCache mKeysCache;
 
@@ -358,6 +368,10 @@ public class Keyboard {
         return mProximityInfo.getNearestKeys(adjustedX, adjustedY);
     }
 
+    public Map<Integer, List<Integer>> getAdditionalProximityChars() {
+        return mAdditionalProximityChars;
+    }
+
     public static String printableCode(int code) {
         switch (code) {
         case CODE_SHIFT: return "shift";
@@ -614,6 +628,7 @@ public class Keyboard {
             mParams = params;
 
             setTouchPositionCorrectionData(context, params);
+            setAdditionalProximityChars(context, params);
 
             params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
             params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
@@ -636,6 +651,25 @@ public class Keyboard {
             params.mTouchPositionCorrection.load(data);
         }
 
+        private static void setAdditionalProximityChars(Context context, Params params) {
+            final String[] additionalChars =
+                    context.getResources().getStringArray(R.array.additional_proximitychars);
+            int currentPrimaryIndex = 0;
+            for (int i = 0; i < additionalChars.length; ++i) {
+                final String additionalChar = additionalChars[i];
+                if (TextUtils.isEmpty(additionalChar)) {
+                    currentPrimaryIndex = 0;
+                } else if (currentPrimaryIndex == 0) {
+                    currentPrimaryIndex = additionalChar.charAt(0);
+                    params.mAdditionalProximityChars.put(
+                            currentPrimaryIndex, new ArrayList<Integer>());
+                } else if (currentPrimaryIndex != 0) {
+                    final int c = additionalChar.charAt(0);
+                    params.mAdditionalProximityChars.get(currentPrimaryIndex).add(c);
+                }
+            }
+        }
+
         public void setAutoGenerate(KeyboardSet.KeysCache keysCache) {
             mParams.mKeysCache = keysCache;
         }
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index c1dae06014661553b58524176d1582a904294ea6..2d1a0083d43d080845483bbdb6f01ab1bb246581 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -24,6 +24,9 @@ import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class ProximityInfo {
@@ -44,7 +47,8 @@ public class ProximityInfo {
     private final Key[][] mGridNeighbors;
 
     ProximityInfo(int gridWidth, int gridHeight, int minWidth, int height, int keyWidth,
-            int keyHeight, Set<Key> keys, TouchPositionCorrection touchPositionCorrection) {
+            int keyHeight, Set<Key> keys, TouchPositionCorrection touchPositionCorrection,
+            Map<Integer, List<Integer>> additionalProximityChars) {
         mGridWidth = gridWidth;
         mGridHeight = gridHeight;
         mGridSize = mGridWidth * mGridHeight;
@@ -58,20 +62,20 @@ public class ProximityInfo {
             // No proximity required. Keyboard might be mini keyboard.
             return;
         }
-        computeNearestNeighbors(keyWidth, keys, touchPositionCorrection);
+        computeNearestNeighbors(keyWidth, keys, touchPositionCorrection, additionalProximityChars);
     }
 
     public static ProximityInfo createDummyProximityInfo() {
-        return new ProximityInfo(1, 1, 1, 1, 1, 1, Collections.<Key>emptySet(), null);
+        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.ROW_SIZE,
-                        480, 300, 11, 3, proximity,
-                        0, null, null, null, null, null, null, null, null);
+                        SpellCheckerProximityInfo.ROW_SIZE, 480, 300, 11, 3, proximity, 0,
+                        null, null, null, null, null, null, null, null);
         return spellCheckerProximityInfo;
     }
 
@@ -79,11 +83,13 @@ public class ProximityInfo {
     static {
         Utils.loadNativeLibrary();
     }
+
     private native long setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
             int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray,
             int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
             int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
             float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii);
+
     private native void releaseProximityInfoNative(long nativeProximityInfo);
 
     private final void setProximityInfo(Key[][] gridNeighborKeys, int keyboardWidth,
@@ -138,7 +144,7 @@ public class ProximityInfo {
                     final float radius = touchPositionCorrection.mRadii[row];
                     sweetSpotCenterXs[i] = hitBoxCenterX + x * hitBoxWidth;
                     sweetSpotCenterYs[i] = hitBoxCenterY + y * hitBoxHeight;
-                    sweetSpotRadii[i] = radius * (float)Math.sqrt(
+                    sweetSpotRadii[i] = radius * (float) Math.sqrt(
                             hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
                 }
             }
@@ -168,7 +174,12 @@ public class ProximityInfo {
     }
 
     private void computeNearestNeighbors(int defaultWidth, Set<Key> keys,
-            TouchPositionCorrection touchPositionCorrection) {
+            TouchPositionCorrection touchPositionCorrection,
+            Map<Integer, List<Integer>> additionalProximityChars) {
+        final Map<Integer, Key> keyCodeMap = new HashMap<Integer, Key>();
+        for (final Key key : keys) {
+            keyCodeMap.put(key.mCode, key);
+        }
         final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
         final int threshold = thresholdBase * thresholdBase;
         // Round-up so we don't have any pixels outside the grid
@@ -186,6 +197,27 @@ public class ProximityInfo {
                         neighborKeys[count++] = key;
                     }
                 }
+                int currentCodesSize = count;
+                for (int i = 0; i < currentCodesSize; ++i) {
+                    final int c = neighborKeys[i].mCode;
+                    final List<Integer> additionalChars = additionalProximityChars.get(c);
+                    if (additionalChars == null || additionalChars.size() == 0) {
+                        continue;
+                    }
+                    for (int j = 0; j < additionalChars.size(); ++j) {
+                        final int additionalChar = additionalChars.get(j);
+                        boolean contains = false;
+                        for (int k = 0; k < count; ++k) {
+                            if(additionalChar == neighborKeys[k].mCode) {
+                                contains = true;
+                                break;
+                            }
+                        }
+                        if (!contains) {
+                            neighborKeys[count++] = keyCodeMap.get(additionalChar);
+                        }
+                    }
+                }
                 mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
                         Arrays.copyOfRange(neighborKeys, 0, count);
             }
@@ -199,7 +231,7 @@ public class ProximityInfo {
             return EMPTY_KEY_ARRAY;
         }
         if (x >= 0 && x < mKeyboardMinWidth && y >= 0 && y < mKeyboardHeight) {
-            int index = (y /  mCellHeight) * mGridWidth + (x / mCellWidth);
+            int index = (y / mCellHeight) * mGridWidth + (x / mCellWidth);
             if (index < mGridSize) {
                 return mGridNeighbors[index];
             }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 230c2916b9b9480e2687525ea5f3c8df322126de..bd244b91333301b3eb160c5258dddc33f241c525 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,8 +16,6 @@
 
 package com.android.inputmethod.latin;
 
-import android.text.TextUtils;
-
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
@@ -33,7 +31,7 @@ public class WordComposer {
     public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
     public static final int NOT_A_COORDINATE = -1;
 
-    final int N = BinaryDictionary.MAX_WORD_LENGTH;
+    final static int N = BinaryDictionary.MAX_WORD_LENGTH;
 
     private ArrayList<int[]> mCodes;
     private int[] mXCoordinates;
diff --git a/native/src/correction.cpp b/native/src/correction.cpp
index 7323747d7da3bc4773802d27fb53aa982307e21b..dafc0fd7f4e278b956badc78ce67c08891231532 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -383,7 +383,10 @@ Correction::CorrectionType Correction::processCharAndCalcState(
             incrementInputIndex();
         } else {
             --mTransposedCount;
-            if (DEBUG_CORRECTION) {
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 DUMP_WORD(mWord, mOutputIndex);
                 AKLOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
                         mTransposedCount, mExcessiveCount, c);
@@ -404,13 +407,17 @@ Correction::CorrectionType Correction::processCharAndCalcState(
             : mProximityInfo->getMatchedProximityId(
                     mInputIndex, c, checkProximityChars, &proximityIndex);
 
-    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
+    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId
+            || ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
         if (canTryCorrection && mOutputIndex > 0
                 && mCorrectionStates[mOutputIndex].mProximityMatching
                 && mCorrectionStates[mOutputIndex].mExceeding
                 && isEquivalentChar(mProximityInfo->getMatchedProximityId(
                         mInputIndex, mWord[mOutputIndex - 1], false))) {
-            if (DEBUG_CORRECTION) {
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
             }
             // Conversion p->e
@@ -429,7 +436,8 @@ Correction::CorrectionType Correction::processCharAndCalcState(
         }
     }
 
-    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
+    if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId
+            || ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
         // TODO: Optimize
         // As the current char turned out to be an unrelated char,
         // we will try other correction-types. Please note that mCorrectionStates[mOutputIndex]
@@ -481,12 +489,47 @@ Correction::CorrectionType Correction::processCharAndCalcState(
                 ++mExcessiveCount;
                 incrementInputIndex();
             }
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+                DUMP_WORD(mWord, mOutputIndex);
+                if (mTransposing) {
+                    AKLOGI("TRANSPOSE: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                            mTransposedCount, mExcessiveCount, c);
+                } else {
+                    AKLOGI("EXCEED: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                            mTransposedCount, mExcessiveCount, c);
+                }
+            }
         } else if (mSkipping) {
             // 3. Skip correction
             ++mSkippedCount;
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+                AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                        mTransposedCount, mExcessiveCount, c);
+            }
             return processSkipChar(c, isTerminal, false);
+        } else if (ProximityInfo::ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
+            // As a last resort, use additional proximity characters
+            mProximityMatching = true;
+            ++mProximityCount;
+            mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+                AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                        mTransposedCount, mExcessiveCount, c);
+            }
         } else {
-            if (DEBUG_CORRECTION) {
+            if (DEBUG_CORRECTION
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 DUMP_WORD(mWord, mOutputIndex);
                 AKLOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
                         mTransposedCount, mExcessiveCount, c);
@@ -506,6 +549,13 @@ Correction::CorrectionType Correction::processCharAndCalcState(
         ++mProximityCount;
         mDistances[mOutputIndex] =
                 mProximityInfo->getNormalizedSquaredDistance(mInputIndex, proximityIndex);
+        if (DEBUG_CORRECTION
+                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
+                        || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
+            AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
+                    mTransposedCount, mExcessiveCount, c);
+        }
     }
 
     addCharToCurrentWord(c);
@@ -539,7 +589,9 @@ Correction::CorrectionType Correction::processCharAndCalcState(
             || isSameAsUserTypedLength) && isTerminal) {
         mTerminalInputIndex = mInputIndex - 1;
         mTerminalOutputIndex = mOutputIndex - 1;
-        if (DEBUG_CORRECTION) {
+        if (DEBUG_CORRECTION
+                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
             DUMP_WORD(mWord, mOutputIndex);
             AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
                     mTransposedCount, mExcessiveCount, c);
@@ -678,7 +730,7 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
     if (excessiveCount > 0) {
         multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
         if (!lastCharExceeded && !proximityInfo->existsAdjacentProximityChars(excessivePos)) {
-            if (DEBUG_CORRECTION_FREQ) {
+            if (DEBUG_DICT_FULL) {
                 AKLOGI("Double excessive demotion");
             }
             // If an excessive character is not adjacent to the left char or the right char,
@@ -687,51 +739,46 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
         }
     }
 
+    const bool performTouchPositionCorrection =
+            CALIBRATE_SCORE_BY_TOUCH_COORDINATES && proximityInfo->touchPositionCorrectionEnabled()
+                        && skippedCount == 0 && excessiveCount == 0 && transposedCount == 0;
     // Score calibration by touch coordinates is being done only for pure-fat finger typing error
     // cases.
     // TODO: Remove this constraint.
-    if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES && proximityInfo->touchPositionCorrectionEnabled()
-            && skippedCount == 0 && excessiveCount == 0 && transposedCount == 0) {
-        for (int i = 0; i < outputLength; ++i) {
-            const int squaredDistance = correction->mDistances[i];
-            if (i < adjustedProximityMatchedCount) {
-                multiplyIntCapped(typedLetterMultiplier, &finalFreq);
-            }
-            if (squaredDistance >= 0) {
-                // Promote or demote the score according to the distance from the sweet spot
-                static const float A = ZERO_DISTANCE_PROMOTION_RATE / 100.0f;
-                static const float B = 1.0f;
-                static const float C = 0.5f;
-                static const float R1 = NEUTRAL_SCORE_SQUARED_RADIUS;
-                static const float R2 = HALF_SCORE_SQUARED_RADIUS;
-                const float x = (float)squaredDistance
-                        / ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
-                const float factor = (x < R1)
-                    ? (A * (R1 - x) + B * x) / R1
-                    : (B * (R2 - x) + C * (x - R1)) / (R2 - R1);
-                // factor is piecewise linear function like:
-                // A -_                  .
-                //     ^-_               .
-                // B      \              .
-                //         \             .
-                // C        \            .
-                //   0   R1 R2
-                if (factor <= 0) {
-                    return -1;
-                }
-                multiplyRate((int)(factor * 100), &finalFreq);
-            } else if (squaredDistance == PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO) {
-                multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
-            }
-        }
-    } else {
-        // Promotion for a word with proximity characters
-        for (int i = 0; i < adjustedProximityMatchedCount; ++i) {
-            // A word with proximity corrections
-            if (DEBUG_DICT_FULL) {
-                AKLOGI("Found a proximity correction.");
-            }
+    for (int i = 0; i < outputLength; ++i) {
+        const int squaredDistance = correction->mDistances[i];
+        if (i < adjustedProximityMatchedCount) {
             multiplyIntCapped(typedLetterMultiplier, &finalFreq);
+        }
+
+        if (performTouchPositionCorrection && squaredDistance >= 0) {
+            // Promote or demote the score according to the distance from the sweet spot
+            static const float A = ZERO_DISTANCE_PROMOTION_RATE / 100.0f;
+            static const float B = 1.0f;
+            static const float C = 0.5f;
+            static const float MIN = 0.3f;
+            static const float R1 = NEUTRAL_SCORE_SQUARED_RADIUS;
+            static const float R2 = HALF_SCORE_SQUARED_RADIUS;
+            const float x = (float)squaredDistance
+                    / ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
+            const float factor = max((x < R1)
+                ? (A * (R1 - x) + B * x) / R1
+                : (B * (R2 - x) + C * (x - R1)) / (R2 - R1), MIN);
+            // factor is piecewise linear function like:
+            // A -_                  .
+            //     ^-_               .
+            // B      \              .
+            //         \_            .
+            // C         ------------.
+            //                       .
+            // 0   R1 R2             .
+            multiplyRate((int)(factor * 100), &finalFreq);
+        } else if (performTouchPositionCorrection
+                && squaredDistance == PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO) {
+            multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
+        } else if (squaredDistance == ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO) {
+            multiplyRate(WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
+        } else if (i < adjustedProximityMatchedCount) {
             multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
         }
     }
@@ -794,7 +841,8 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
         AKLOGI("calc: %d, %d", outputLength, sameLength);
     }
 
-    if (DEBUG_CORRECTION_FREQ) {
+    if (DEBUG_CORRECTION_FREQ
+            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
         DUMP_WORD(correction->mWord, outputLength);
         AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
                 skippedCount, transposedCount, excessiveCount, outputLength, lastCharExceeded,
diff --git a/native/src/correction.h b/native/src/correction.h
index b246070fee7c0f6fe02623d1ce7a3f0ad16680fd..a711c994dd0e31496c74f5fb2e7082a5040cc670 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -85,7 +85,7 @@ class Correction {
         }
     }
 
-    Correction(const int typedLetterMultiplier, const int fullWordMultiplier);
+            Correction(const int typedLetterMultiplier, const int fullWordMultiplier);
     void initCorrection(
             const ProximityInfo *pi, const int inputLength, const int maxWordLength);
     void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
diff --git a/native/src/defines.h b/native/src/defines.h
index 3f3f5ba5c20687391f12974fdc035cc9fff851dd..02c1fe0a22bdb0f921108b6521161f12f370d667 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -172,6 +172,7 @@ static void prof_out(void) {
 #define NOT_A_COORDINATE -1
 #define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2
 #define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3
+#define ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO -4
 #define NOT_A_INDEX -1
 #define NOT_A_FREQUENCY -1
 
@@ -194,6 +195,7 @@ static void prof_out(void) {
 #define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 70
 #define FULL_MATCHED_WORDS_PROMOTION_RATE 120
 #define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
+#define WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE 30
 #define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105
 #define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE 160
 #define CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE 45
@@ -210,6 +212,9 @@ static void prof_out(void) {
 // This is only used for the size of array. Not to be used in c functions.
 #define MAX_WORD_LENGTH_INTERNAL 48
 
+// This must be equal to ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE in KeyDetector.java
+#define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
+
 // Word limit for sub queues used in WordsPriorityQueuePool.  Sub queues are temporary queues used
 // for better performance.
 // Holds up to 1 candidate for each word
@@ -241,4 +246,8 @@ static void prof_out(void) {
 // The ratio of neutral area radius to sweet spot radius.
 #define NEUTRAL_AREA_RADIUS_RATIO 1.3f
 
+// DEBUG
+#define INPUTLENGTH_FOR_DEBUG -1
+#define MIN_OUTPUT_INDEX_FOR_DEBUG -1
+
 #endif // LATINIME_DEFINES_H
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
index e0e93809976b074595aad60cc761248cdb44a268..b6bab2274816630e8f7fdb40562446a4f47c7b00 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -261,7 +261,8 @@ ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId(const int inde
 
     // Not an exact nor an accent-alike match: search the list of close keys
     int j = 1;
-    while (j < MAX_PROXIMITY_CHARS_SIZE && currentChars[j] > 0) {
+    while (j < MAX_PROXIMITY_CHARS_SIZE
+            && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
         const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
         if (matched) {
             if (proximityIndex) {
@@ -271,6 +272,21 @@ ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId(const int inde
         }
         ++j;
     }
+    if (j < MAX_PROXIMITY_CHARS_SIZE
+            && currentChars[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+        ++j;
+        while (j < MAX_PROXIMITY_CHARS_SIZE
+                && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+            const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
+            if (matched) {
+                if (proximityIndex) {
+                    *proximityIndex = j;
+                }
+                return ADDITIONAL_PROXIMITY_CHAR;
+            }
+            ++j;
+        }
+    }
 
     // Was not included, signal this as an unrelated character.
     return UNRELATED_CHAR;
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index 9ca5505a7709da3049782429cdcbf582568475ed..b77c1bb0aa08f5774eb028da7602e569e0fd07d5 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -38,7 +38,9 @@ class ProximityInfo {
         // It is a char located nearby on the keyboard
         NEAR_PROXIMITY_CHAR,
         // It is an unrelated char
-        UNRELATED_CHAR
+        UNRELATED_CHAR,
+        // Additional proximity char which can differ by language.
+        ADDITIONAL_PROXIMITY_CHAR
     } ProximityType;
 
     ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,