merge in jb-mr1-release history after reset to jb-mr1-dev
diff --git a/dictionaries/en_whitelist.xml b/dictionaries/en_whitelist.xml
new file mode 100644
index 0000000..e11935f
--- /dev/null
+++ b/dictionaries/en_whitelist.xml
@@ -0,0 +1,297 @@
+<?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.
+*/
+-->
+<shortcuts>
+ <entry shortcut="ill">
+  <target replacement="I'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="acomodate">
+  <target replacement="accommodate" priority="whitelist" />
+ </entry>
+ <entry shortcut="aint">
+  <target replacement="ain't" priority="whitelist" />
+ </entry>
+ <entry shortcut="alot">
+  <target replacement="a lot" priority="whitelist" />
+ </entry>
+ <entry shortcut="andteh">
+  <target replacement="and the" priority="whitelist" />
+ </entry>
+ <entry shortcut="arent">
+  <target replacement="aren't" priority="whitelist" />
+ </entry>
+ <entry shortcut="bern">
+  <target replacement="been" priority="whitelist" />
+ </entry>
+ <entry shortcut="bot">
+  <target replacement="not" priority="whitelist" />
+ </entry>
+ <entry shortcut="bur">
+  <target replacement="but" priority="whitelist" />
+ </entry>
+ <entry shortcut="cam">
+  <target replacement="can" priority="whitelist" />
+ </entry>
+ <entry shortcut="cant">
+  <target replacement="can't" priority="whitelist" />
+ </entry>
+ <entry shortcut="dame">
+  <target replacement="same" priority="whitelist" />
+ </entry>
+ <entry shortcut="didint">
+  <target replacement="didn't" priority="whitelist" />
+ </entry>
+ <entry shortcut="dormer">
+  <target replacement="former" priority="whitelist" />
+ </entry>
+ <entry shortcut="dud">
+  <target replacement="did" priority="whitelist" />
+ </entry>
+ <entry shortcut="fay">
+  <target replacement="day" priority="whitelist" />
+ </entry>
+ <entry shortcut="fife">
+  <target replacement="five" priority="whitelist" />
+ </entry>
+ <entry shortcut="foo">
+  <target replacement="for" priority="whitelist" />
+ </entry>
+ <entry shortcut="fora">
+  <target replacement="for a" priority="whitelist" />
+ </entry>
+ <entry shortcut="galled">
+  <target replacement="called" priority="whitelist" />
+ </entry>
+ <entry shortcut="goo">
+  <target replacement="too" priority="whitelist" />
+ </entry>
+ <entry shortcut="hed">
+  <target replacement="he'd" priority="whitelist" />
+ </entry>
+ <entry shortcut="hel">
+  <target replacement="he'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="heres">
+  <target replacement="here's" priority="whitelist" />
+ </entry>
+ <entry shortcut="hew">
+  <target replacement="new" priority="whitelist" />
+ </entry>
+ <entry shortcut="hoe">
+  <target replacement="how" priority="whitelist" />
+ </entry>
+ <entry shortcut="hoes">
+  <target replacement="how's" priority="whitelist" />
+ </entry>
+ <entry shortcut="howd">
+  <target replacement="how'd" priority="whitelist" />
+ </entry>
+ <entry shortcut="howll">
+  <target replacement="how'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="hows">
+  <target replacement="how's" priority="whitelist" />
+ </entry>
+ <entry shortcut="howve">
+  <target replacement="how've" priority="whitelist" />
+ </entry>
+ <entry shortcut="hum">
+  <target replacement="him" priority="whitelist" />
+ </entry>
+ <entry shortcut="i">
+  <target replacement="I" priority="whitelist" />
+ </entry>
+ <entry shortcut="ifs">
+  <target replacement="its" priority="whitelist" />
+ </entry>
+ <entry shortcut="il">
+  <target replacement="I'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="im">
+  <target replacement="I'm" priority="whitelist" />
+ </entry>
+ <entry shortcut="inteh">
+  <target replacement="in the" priority="whitelist" />
+ </entry>
+ <entry shortcut="itd">
+  <target replacement="it'd" priority="whitelist" />
+ </entry>
+ <entry shortcut="itsa">
+  <target replacement="it's a" priority="whitelist" />
+ </entry>
+ <entry shortcut="lets">
+  <target replacement="let's" priority="whitelist" />
+ </entry>
+ <entry shortcut="maam">
+  <target replacement="ma'am" priority="whitelist" />
+ </entry>
+ <entry shortcut="manu">
+  <target replacement="many" priority="whitelist" />
+ </entry>
+ <entry shortcut="mare">
+  <target replacement="made" priority="whitelist" />
+ </entry>
+ <entry shortcut="mew">
+  <target replacement="new" priority="whitelist" />
+ </entry>
+ <entry shortcut="mire">
+  <target replacement="more" priority="whitelist" />
+ </entry>
+ <entry shortcut="moat">
+  <target replacement="most" priority="whitelist" />
+ </entry>
+ <entry shortcut="mot">
+  <target replacement="not" priority="whitelist" />
+ </entry>
+ <entry shortcut="mote">
+  <target replacement="note" priority="whitelist" />
+ </entry>
+ <entry shortcut="motes">
+  <target replacement="notes" priority="whitelist" />
+ </entry>
+ <entry shortcut="mow">
+  <target replacement="now" priority="whitelist" />
+ </entry>
+ <entry shortcut="namer">
+  <target replacement="named" priority="whitelist" />
+ </entry>
+ <entry shortcut="nave">
+  <target replacement="have" priority="whitelist" />
+ </entry>
+ <entry shortcut="nee">
+  <target replacement="new" priority="whitelist" />
+ </entry>
+ <entry shortcut="nigh">
+  <target replacement="high" priority="whitelist" />
+ </entry>
+ <entry shortcut="nit">
+  <target replacement="not" priority="whitelist" />
+ </entry>
+ <entry shortcut="oft">
+  <target replacement="off" priority="whitelist" />
+ </entry>
+ <entry shortcut="os">
+  <target replacement="is" priority="whitelist" />
+ </entry>
+ <entry shortcut="pater">
+  <target replacement="later" priority="whitelist" />
+ </entry>
+ <entry shortcut="rook">
+  <target replacement="took" priority="whitelist" />
+ </entry>
+ <entry shortcut="shel">
+  <target replacement="she'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="shouldent">
+  <target replacement="shouldn't" priority="whitelist" />
+ </entry>
+ <entry shortcut="sill">
+  <target replacement="will" priority="whitelist" />
+ </entry>
+ <entry shortcut="sown">
+  <target replacement="down" priority="whitelist" />
+ </entry>
+ <entry shortcut="thatd">
+  <target replacement="that'd" priority="whitelist" />
+ </entry>
+ <entry shortcut="tine">
+  <target replacement="time" priority="whitelist" />
+ </entry>
+ <entry shortcut="thong">
+  <target replacement="thing" priority="whitelist" />
+ </entry>
+ <entry shortcut="tome">
+  <target replacement="time" priority="whitelist" />
+ </entry>
+ <entry shortcut="uf">
+  <target replacement="if" priority="whitelist" />
+ </entry>
+ <entry shortcut="un">
+  <target replacement="in" priority="whitelist" />
+ </entry>
+ <entry shortcut="UnitedStates">
+  <target replacement="United States" priority="whitelist" />
+ </entry>
+ <entry shortcut="unitedstates">
+  <target replacement="United States" priority="whitelist" />
+ </entry>
+ <entry shortcut="visavis">
+  <target replacement="vis-a-vis" priority="whitelist" />
+ </entry>
+ <entry shortcut="wierd">
+  <target replacement="weird" priority="whitelist" />
+ </entry>
+ <entry shortcut="wel">
+  <target replacement="we'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="wer">
+  <target replacement="we're" priority="whitelist" />
+ </entry>
+ <entry shortcut="whatd">
+  <target replacement="what'd" priority="whitelist" />
+ </entry>
+ <entry shortcut="whatm">
+  <target replacement="what'm" priority="whitelist" />
+ </entry>
+ <entry shortcut="whatre">
+  <target replacement="what're" priority="whitelist" />
+ </entry>
+ <entry shortcut="whats">
+  <target replacement="what's" priority="whitelist" />
+ </entry>
+ <entry shortcut="whens">
+  <target replacement="when's" priority="whitelist" />
+ </entry>
+ <entry shortcut="whered">
+  <target replacement="where'd" priority="whitelist" />
+ </entry>
+ <entry shortcut="wherell">
+  <target replacement="where'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="wheres">
+  <target replacement="where's" priority="whitelist" />
+ </entry>
+ <entry shortcut="wholl">
+  <target replacement="who'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="whove">
+  <target replacement="who've" priority="whitelist" />
+ </entry>
+ <entry shortcut="whyd">
+  <target replacement="why'd" priority="whitelist" />
+ </entry>
+ <entry shortcut="whyll">
+  <target replacement="why'll" priority="whitelist" />
+ </entry>
+ <entry shortcut="whys">
+  <target replacement="why's" priority="whitelist" />
+ </entry>
+ <entry shortcut="whyve">
+  <target replacement="why've" priority="whitelist" />
+ </entry>
+ <entry shortcut="wont">
+  <target replacement="won't" priority="whitelist" />
+ </entry>
+ <entry shortcut="yall">
+  <target replacement="y'all" priority="whitelist" />
+ </entry>
+ <entry shortcut="youd">
+  <target replacement="you'd" priority="whitelist" />
+ </entry>
+</shortcuts>
diff --git a/java/proguard.flags b/java/proguard.flags
index 24b4c19..ac5b7df 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -44,6 +44,10 @@
   <init>(...);
 }
 
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
 -keep class com.android.inputmethod.research.ResearchLogger {
   void flush();
   void publishCurrentLogUnit(...);
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 254c2d2..56642c4 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -46,9 +46,9 @@
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"एड-ऑन डिक्शनरी"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"मुख्‍य डिक्‍शनरी"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"सुधार सुझाव दिखाएं"</string>
-    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"लिखते समय सुझाए गए शब्‍द प्रदर्शित करें"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"लिखते समय सुझाए गए शब्‍द दिखाएं"</string>
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"हमेशा दिखाएं"</string>
-    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"र्पोट्रेट मोड पर प्रदर्शित करें"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3551821800439659812">"र्पोट्रेट मोड पर दिखाएं"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"हमेशा छुपाएं"</string>
     <string name="auto_correction" msgid="4979925752001319458">"स्‍वत: सुधार"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spacebar और विराम चिह्न गलत लिखे गए शब्‍दों को स्‍वचालित रूप से ठीक करते हैं"</string>
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 4088f3e..9590290 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -835,20 +835,6 @@
         return false;
     }
 
-    @Override
-    public void draw(Canvas c) {
-        Utils.GCUtils.getInstance().reset();
-        boolean tryGC = true;
-        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
-            try {
-                super.draw(c);
-                tryGC = false;
-            } catch (OutOfMemoryError e) {
-                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(TAG, e);
-            }
-        }
-    }
-
     /**
      * Receives hover events from the input framework.
      *
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 85360c4..7d565a6 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -29,7 +29,6 @@
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
 
@@ -130,10 +129,6 @@
     private static final InputPointers sAggregratedPointers = new InputPointers(
             GestureStroke.DEFAULT_CAPACITY);
     private static PointerTrackerQueue sPointerTrackerQueue;
-    // HACK: Change gesture detection criteria depending on this variable.
-    // TODO: Find more comprehensive ways to detect a gesture start.
-    // True when the previous user input was a gesture input, not a typing input.
-    private static boolean sWasInGesture;
 
     public final int mPointerId;
 
@@ -413,8 +408,7 @@
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
         mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
-        mGestureStroke.setGestureSampleLength(
-                mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight);
+        mGestureStroke.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
         final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
         if (newKey != mCurrentKey) {
             if (mDrawingProxy != null) {
@@ -526,7 +520,7 @@
 
     public void drawGestureTrail(Canvas canvas, Paint paint) {
         if (mInGesture) {
-            mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY);
+            mGestureStroke.drawGestureTrail(canvas, paint);
         }
     }
 
@@ -586,7 +580,6 @@
         mListener.onEndBatchInput(batchPoints);
         clearBatchInputRecognitionStateOfThisPointerTracker();
         clearBatchInputPointsOfAllPointerTrackers();
-        sWasInGesture = true;
     }
 
     private void abortBatchInput() {
@@ -719,7 +712,7 @@
         if (sShouldHandleGesture && mIsPossibleGesture) {
             final GestureStroke stroke = mGestureStroke;
             stroke.addPoint(x, y, gestureTime, isHistorical);
-            if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) {
+            if (!mInGesture && stroke.isStartOfAGesture()) {
                 startBatchInput();
             }
         }
@@ -1002,7 +995,6 @@
         int code = key.mCode;
         callListenerOnCodeInput(key, code, x, y);
         callListenerOnRelease(key, code, false);
-        sWasInGesture = false;
     }
 
     private void printTouchEvent(String title, int x, int y, long eventTime) {
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index ae123e2..ac0a56b 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -18,7 +18,6 @@
 
 import android.graphics.Rect;
 import android.text.TextUtils;
-import android.util.FloatMath;
 
 import com.android.inputmethod.keyboard.Keyboard.Params.TouchPositionCorrection;
 import com.android.inputmethod.latin.JniUtils;
@@ -155,7 +154,9 @@
                     final float radius = touchPositionCorrection.mRadii[row];
                     sweetSpotCenterXs[i] = hitBox.exactCenterX() + x * hitBoxWidth;
                     sweetSpotCenterYs[i] = hitBox.exactCenterY() + y * hitBoxHeight;
-                    sweetSpotRadii[i] = radius * FloatMath.sqrt(
+                    // Note that, in recent versions of Android, FloatMath is actually slower than
+                    // java.lang.Math due to the way the JIT optimizes java.lang.Math.
+                    sweetSpotRadii[i] = radius * (float)Math.sqrt(
                             hitBoxWidth * hitBoxWidth + hitBoxHeight * hitBoxHeight);
                 }
             }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 28d6c1d..79e977a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -16,7 +16,6 @@
 
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.util.FloatMath;
 
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.InputPointers;
@@ -38,19 +37,16 @@
     private int mLastPointY;
 
     private int mMinGestureLength;
-    private int mMinGestureLengthWhileInGesture;
     private int mMinGestureSampleLength;
 
     // TODO: Move some of these to resource.
-    private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f;
-    private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f;
-    private static final int MIN_GESTURE_DURATION = 150; // msec
-    private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec
-    private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f;
+    private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 0.75f;
+    private static final int MIN_GESTURE_DURATION = 100; // msec
+    private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH = 1.0f / 6.0f;
     private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec
     private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f);
 
-    private static final float DOUBLE_PI = (float)(2 * Math.PI);
+    private static final float DOUBLE_PI = (float)(2.0f * Math.PI);
 
     // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT
     private static final int DRAWING_GESTURE_FADE_START = 10;
@@ -61,21 +57,15 @@
         reset();
     }
 
-    public void setGestureSampleLength(final int keyWidth, final int keyHeight) {
+    public void setGestureSampleLength(final int keyWidth) {
         // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key?
         mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH);
-        mMinGestureLengthWhileInGesture = (int)(
-                keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE);
-        mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT);
+        mMinGestureSampleLength = (int)(keyWidth * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_WIDTH);
     }
 
-    public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) {
-        // The tolerance of the time duration and the stroke length to detect the start of a
-        // gesture stroke should be eased when the previous input was a gesture input.
-        if (wasInGesture) {
-            return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE
-                    && mLength > mMinGestureLengthWhileInGesture;
-        }
+    public boolean isStartOfAGesture() {
+        final int size = mEventTimes.getLength();
+        final int downDuration = (size > 0) ? mEventTimes.get(size - 1) : 0;
         return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength;
     }
 
@@ -154,19 +144,21 @@
         mLastIncrementalBatchSize = size;
     }
 
-    private static float getDistance(final int p1x, final int p1y,
-            final int p2x, final int p2y) {
-        final float dx = p1x - p2x;
-        final float dy = p1y - p2y;
-        // TODO: Optimize out this {@link FloatMath#sqrt(float)} call.
-        return FloatMath.sqrt(dx * dx + dy * dy);
+    private static float getDistance(final int x1, final int y1, final int x2, final int y2) {
+        final float dx = x1 - x2;
+        final float dy = y1 - y2;
+        // Note that, in recent versions of Android, FloatMath is actually slower than
+        // java.lang.Math due to the way the JIT optimizes java.lang.Math.
+        return (float)Math.sqrt(dx * dx + dy * dy);
     }
 
-    private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) {
-        final int dx = p1x - p2x;
-        final int dy = p1y - p2y;
+    private static float getAngle(final int x1, final int y1, final int x2, final int y2) {
+        final int dx = x1 - x2;
+        final int dy = y1 - y2;
         if (dx == 0 && dy == 0) return 0;
-        return (float)Math.atan2(dy, dx);
+        // Would it be faster to call atan2f() directly via JNI?  Not sure about what the JIT
+        // does with Math.atan2().
+        return (float)Math.atan2((double)dy, (double)dx);
     }
 
     private static float getAngleDiff(final float a1, final float a2) {
@@ -177,12 +169,12 @@
         return diff;
     }
 
-    public void drawGestureTrail(Canvas canvas, Paint paint, int lastX, int lastY) {
+    public void drawGestureTrail(final Canvas canvas, final Paint paint) {
         // TODO: These paint parameter interpolation should be tunable, possibly introduce an object
         // that implements an interface such as Paint getPaint(int step, int strokePoints)
         final int size = mXCoordinates.getLength();
-        int[] xCoords = mXCoordinates.getPrimitiveArray();
-        int[] yCoords = mYCoordinates.getPrimitiveArray();
+        final int[] xCoords = mXCoordinates.getPrimitiveArray();
+        final int[] yCoords = mYCoordinates.getPrimitiveArray();
         int alpha = Constants.Color.ALPHA_OPAQUE;
         for (int i = size - 1; i > 0 && alpha > 0; i--) {
             paint.setAlpha(alpha);
@@ -190,9 +182,6 @@
                 alpha -= DRAWING_GESTURE_FADE_RATE;
             }
             canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint);
-            if (i == size - 1) {
-                canvas.drawLine(lastX, lastY, xCoords[i], yCoords[i], paint);
-            }
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index a663374..0481668 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -75,17 +75,10 @@
         return maxFreq;
     }
 
-    // Returns true if this is a whitelist entry, or it isn't in any dictionary.
-    public static boolean isWhitelistedOrNotAWord(
+    // Returns true if this isn't in any dictionary.
+    public static boolean isNotAWord(
             final ConcurrentHashMap<String, Dictionary> dictionaries,
             final CharSequence word, final boolean ignoreCase) {
-        final WhitelistDictionary whitelistDictionary =
-                (WhitelistDictionary)dictionaries.get(Dictionary.TYPE_WHITELIST);
-        // If "word" is in the whitelist dictionary, it should not be auto corrected.
-        if (whitelistDictionary != null
-                && whitelistDictionary.shouldForciblyAutoCorrectFrom(word)) {
-            return true;
-        }
         return !isValidWord(dictionaries, word, ignoreCase);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index e0adc9a..f0f5cd3 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -51,7 +51,8 @@
     private static final int TYPED_LETTER_MULTIPLIER = 2;
 
     private long mNativeDict;
-    private final int[] mInputCodes = new int[MAX_WORD_LENGTH];
+    private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
+    // TODO: The below should be int[] mOutputCodePoints
     private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS];
     private final int[] mSpaceIndices = new int[MAX_SPACES];
     private final int[] mOutputScores = new int[MAX_RESULTS];
@@ -78,6 +79,7 @@
         mUseFullEditDistance = useFullEditDistance;
         loadDictionary(filename, offset, length);
         mDicTraverseSession = new DicTraverseSession(locale);
+        mDicTraverseSession.initSession(mNativeDict);
     }
 
     static {
@@ -88,18 +90,17 @@
             int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
             int maxPredictions);
     private native void closeNative(long dict);
-    private native int getFrequencyNative(long dict, int[] word, int wordLength);
+    private native int getFrequencyNative(long dict, int[] word);
     private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
     private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
             int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
-            int[] inputCodes, int codesSize, int commitPoint, boolean isGesture,
+            int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture,
             int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars,
             int[] outputScores, int[] outputIndices, int[] outputTypes);
-    private static native float calcNormalizedScoreNative(
-            char[] before, int beforeLength, char[] after, int afterLength, int score);
-    private static native int editDistanceNative(
-            char[] before, int beforeLength, char[] after, int afterLength);
+    private static native float calcNormalizedScoreNative(char[] before, char[] after, int score);
+    private static native int editDistanceNative(char[] before, char[] after);
 
+    // TODO: Move native dict into session
     private final void loadDictionary(String path, long startOffset, long length) {
         mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER,
                 FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS);
@@ -109,9 +110,7 @@
     public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
             final CharSequence prevWord, final ProximityInfo proximityInfo) {
         if (!isValidDictionary()) return null;
-        Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
-        Arrays.fill(mOutputChars, (char) 0);
-        Arrays.fill(mOutputScores, 0);
+        Arrays.fill(mInputCodePoints, WordComposer.NOT_A_CODE);
         // TODO: toLowerCase in the native code
         final int[] prevWordCodePointArray = (null == prevWord)
                 ? null : StringUtils.toCodePointArray(prevWord.toString());
@@ -121,7 +120,7 @@
         if (composerSize <= 1 || !isGesture) {
             if (composerSize > MAX_WORD_LENGTH - 1) return null;
             for (int i = 0; i < composerSize; i++) {
-                mInputCodes[i] = composer.getCodeAt(i);
+                mInputCodePoints[i] = composer.getCodeAt(i);
             }
         }
 
@@ -129,10 +128,9 @@
         final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
         // proximityInfo and/or prevWordForBigrams may not be null.
         final int tmpCount = getSuggestionsNative(mNativeDict,
-                proximityInfo.getNativeProximityInfo(),
-                mDicTraverseSession.getSession(), ips.getXCoordinates(),
-                ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
-                mInputCodes, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
+                proximityInfo.getNativeProximityInfo(), mDicTraverseSession.getSession(),
+                ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
+                mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
                 mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes);
         final int count = Math.min(tmpCount, MAX_PREDICTIONS);
 
@@ -145,9 +143,10 @@
                 ++len;
             }
             if (len > 0) {
+                final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j]
+                        ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
                 suggestions.add(new SuggestedWordInfo(
-                        new String(mOutputChars, start, len),
-                        mOutputScores[j], SuggestedWordInfo.KIND_CORRECTION, mDictType));
+                        new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType));
             }
         }
         return suggestions;
@@ -158,13 +157,11 @@
     }
 
     public static float calcNormalizedScore(String before, String after, int score) {
-        return calcNormalizedScoreNative(before.toCharArray(), before.length(),
-                after.toCharArray(), after.length(), score);
+        return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score);
     }
 
     public static int editDistance(String before, String after) {
-        return editDistanceNative(
-                before.toCharArray(), before.length(), after.toCharArray(), after.length());
+        return editDistanceNative(before.toCharArray(), after.toCharArray());
     }
 
     @Override
@@ -175,8 +172,8 @@
     @Override
     public int getFrequency(CharSequence word) {
         if (word == null) return -1;
-        int[] chars = StringUtils.toCodePointArray(word.toString());
-        return getFrequencyNative(mNativeDict, chars, chars.length);
+        int[] codePoints = StringUtils.toCodePointArray(word.toString());
+        return getFrequencyNative(mNativeDict, codePoints);
     }
 
     // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index 437876e..c768153 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -23,8 +23,8 @@
         JniUtils.loadNativeLibrary();
     }
     private native long setDicTraverseSessionNative(String locale);
-    private native void initDicTraverseSessionNative(
-            long nativeDicTraverseSession, int[] previousWord, int previwousWordLength);
+    private native void initDicTraverseSessionNative(long nativeDicTraverseSession,
+            long dictionary, int[] previousWord, int previousWordLength);
     private native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
 
     private long mNativeDicTraverseSession;
@@ -32,19 +32,19 @@
     public DicTraverseSession(Locale locale) {
         mNativeDicTraverseSession = createNativeDicTraverseSession(
                 locale != null ? locale.toString() : "");
-        initSession();
     }
 
     public long getSession() {
         return mNativeDicTraverseSession;
     }
 
-    public void initSession() {
-        initSession(null, 0);
+    public void initSession(long dictionary) {
+        initSession(dictionary, null, 0);
     }
 
-    public void initSession(int[] previousWord, int previousWordLength) {
-        initDicTraverseSessionNative(mNativeDicTraverseSession, previousWord, previousWordLength);
+    public void initSession(long dictionary, int[] previousWord, int previousWordLength) {
+        initDicTraverseSessionNative(
+                mNativeDicTraverseSession, dictionary, previousWord, previousWordLength);
     }
 
     private final long createNativeDicTraverseSession(String locale) {
diff --git a/java/src/com/android/inputmethod/latin/NativeUtils.java b/java/src/com/android/inputmethod/latin/NativeUtils.java
deleted file mode 100644
index 9cc2bc0..0000000
--- a/java/src/com/android/inputmethod/latin/NativeUtils.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.inputmethod.latin;
-
-public class NativeUtils {
-    static {
-        JniUtils.loadNativeLibrary();
-    }
-
-    private NativeUtils() {
-        // This utility class is not publicly instantiable.
-    }
-
-    /**
-     * This method just calls up libm's powf() directly.
-     */
-    public static native float powf(float x, float y);
-}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index a65d36a..8a2341d 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -208,22 +208,33 @@
                     wordComposerForLookup, prevWordForBigram, proximityInfo));
         }
 
+        final CharSequence whitelistedWordFromWhitelistDictionary =
+                mWhiteListDictionary.getWhitelistedWord(consideredWord);
+        if (whitelistedWordFromWhitelistDictionary != null) {
+            // MAX_SCORE ensures this will be considered strong enough to be auto-corrected
+            suggestionsSet.add(new SuggestedWordInfo(whitelistedWordFromWhitelistDictionary,
+                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
+                    Dictionary.TYPE_WHITELIST));
+        }
+
+        final CharSequence whitelistedWord;
+        if (suggestionsSet.isEmpty()) {
+            whitelistedWord = null;
+        } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) {
+            whitelistedWord = null;
+        } else {
+            whitelistedWord = suggestionsSet.first().mWord;
+        }
+
         // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
         // but still autocorrected from - in the case the whitelist only capitalizes the word.
         // The whitelist should be case-insensitive, so it's not possible to be consistent with
         // a boolean flag. Right now this is handled with a slight hack in
         // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
-        final boolean allowsToBeAutoCorrected = AutoCorrection.isWhitelistedOrNotAWord(
-                mDictionaries, consideredWord, wordComposer.isFirstCharCapitalized());
-
-        final CharSequence whitelistedWord =
-                mWhiteListDictionary.getWhitelistedWord(consideredWord);
-        if (whitelistedWord != null) {
-            // MAX_SCORE ensures this will be considered strong enough to be auto-corrected
-            suggestionsSet.add(new SuggestedWordInfo(whitelistedWord,
-                    SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST,
-                    Dictionary.TYPE_WHITELIST));
-        }
+        final boolean allowsToBeAutoCorrected = (null != whitelistedWord
+                && !whitelistedWord.equals(consideredWord))
+                || AutoCorrection.isNotAWord(mDictionaries, consideredWord,
+                        wordComposer.isFirstCharCapitalized());
 
         final boolean hasAutoCorrection;
         // TODO: using isCorrectionEnabled here is not very good. It's probably useless, because
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
index 1de95d7..5a2fdf4 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
@@ -212,7 +212,7 @@
                 for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
                     final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
                     final float freq = initialFreq
-                            * NativeUtils.powf(initialFreq, elapsedHours / HALF_LIFE_HOURS);
+                            * (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
                     final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
                     SCORE_TABLE[i][j] = intFreq;
                 }
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 1b7301b..1725a76 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -37,7 +37,6 @@
     com_android_inputmethod_keyboard_ProximityInfo.cpp \
     com_android_inputmethod_latin_BinaryDictionary.cpp \
     com_android_inputmethod_latin_DicTraverseSession.cpp \
-    com_android_inputmethod_latin_NativeUtils.cpp \
     jni_common.cpp
 
 LATIN_IME_CORE_SRC_FILES := \
@@ -47,6 +46,7 @@
     char_utils.cpp \
     correction.cpp \
     dictionary.cpp \
+    dic_traverse_wrapper.cpp \
     proximity_info.cpp \
     proximity_info_state.cpp \
     unigram_dictionary.cpp \
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index f9b23f0..545d91a 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -43,8 +43,8 @@
 
 static JNINativeMethod sKeyboardMethods[] = {
     {"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}
+            reinterpret_cast<void *>(latinime_Keyboard_setProximityInfo)},
+    {"releaseProximityInfoNative", "(J)V", reinterpret_cast<void *>(latinime_Keyboard_release)}
 };
 
 int register_ProximityInfo(JNIEnv *env) {
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 6e2efbe..5a287a1 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -14,15 +14,12 @@
  * limitations under the License.
  */
 
+
+#include <cstring> // for memset()
+
 #define LOG_TAG "LatinIME: jni: BinaryDictionary"
 
-#include "binary_format.h"
-#include "com_android_inputmethod_latin_BinaryDictionary.h"
-#include "correction.h"
-#include "defines.h"
-#include "dictionary.h"
-#include "jni.h"
-#include "jni_common.h"
+#include "defines.h" // for macros below
 
 #ifdef USE_MMAP_FOR_DICTIONARY
 #include <cerrno>
@@ -32,6 +29,13 @@
 #include <cstdlib>
 #endif // USE_MMAP_FOR_DICTIONARY
 
+#include "binary_format.h"
+#include "com_android_inputmethod_latin_BinaryDictionary.h"
+#include "correction.h"
+#include "dictionary.h"
+#include "jni.h"
+#include "jni_common.h"
+
 namespace latinime {
 
 class ProximityInfo;
@@ -71,7 +75,7 @@
         AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
         return 0;
     }
-    dictBuf = (void *)((char *)dictBuf + adjust);
+    dictBuf = reinterpret_cast<void *>(reinterpret_cast<char *>(dictBuf) + adjust);
 #else // USE_MMAP_FOR_DICTIONARY
     /* malloc version */
     FILE *file = 0;
@@ -106,10 +110,11 @@
         return 0;
     }
     Dictionary *dictionary = 0;
-    if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
+    if (BinaryFormat::UNKNOWN_FORMAT
+            == BinaryFormat::detectFormat(reinterpret_cast<uint8_t *>(dictBuf))) {
         AKLOGE("DICT: dictionary format is unknown, bad magic number");
 #ifdef USE_MMAP_FOR_DICTIONARY
-        releaseDictBuf(((char*)dictBuf) - adjust, adjDictSize, fd);
+        releaseDictBuf(reinterpret_cast<char *>(dictBuf) - adjust, adjDictSize, fd);
 #else // USE_MMAP_FOR_DICTIONARY
         releaseDictBuf(dictBuf, 0, 0);
 #endif // USE_MMAP_FOR_DICTIONARY
@@ -124,106 +129,132 @@
 
 static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
         jlong proximityInfo, jlong dicTraverseSession, jintArray xCoordinatesArray,
-        jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdArray,
-        jintArray inputArray, jint arraySize, jint commitPoint, jboolean isGesture,
-        jintArray prevWordForBigrams, jboolean useFullEditDistance, jcharArray outputArray,
-        jintArray frequencyArray, jintArray spaceIndexArray, jintArray outputTypesArray) {
+        jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray,
+        jintArray inputCodePointsArray, jint arraySize, jint commitPoint, jboolean isGesture,
+        jintArray prevWordCodePointsForBigrams, jboolean useFullEditDistance,
+        jcharArray outputCharsArray, jintArray scoresArray, jintArray spaceIndicesArray,
+        jintArray outputTypesArray) {
     Dictionary *dictionary = reinterpret_cast<Dictionary*>(dict);
     if (!dictionary) return 0;
     ProximityInfo *pInfo = reinterpret_cast<ProximityInfo*>(proximityInfo);
     void *traverseSession = reinterpret_cast<void*>(dicTraverseSession);
-    int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, 0);
-    int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, 0);
-    int *times = env->GetIntArrayElements(timesArray, 0);
-    int *pointerIds = env->GetIntArrayElements(pointerIdArray, 0);
-    int *frequencies = env->GetIntArrayElements(frequencyArray, 0);
-    int *inputCodes = env->GetIntArrayElements(inputArray, 0);
-    jchar *outputChars = env->GetCharArrayElements(outputArray, 0);
-    int *spaceIndices = env->GetIntArrayElements(spaceIndexArray, 0);
-    int *outputTypes = env->GetIntArrayElements(outputTypesArray, 0);
-    jint *prevWordChars = prevWordForBigrams
-            ? env->GetIntArrayElements(prevWordForBigrams, 0) : 0;
-    jsize prevWordLength = prevWordChars ? env->GetArrayLength(prevWordForBigrams) : 0;
+
+    // Input values
+    int xCoordinates[arraySize];
+    int yCoordinates[arraySize];
+    int times[arraySize];
+    int pointerIds[arraySize];
+    const jsize inputCodePointsLength = env->GetArrayLength(inputCodePointsArray);
+    int inputCodePoints[inputCodePointsLength];
+    const jsize prevWordCodePointsLength =
+            prevWordCodePointsForBigrams ? env->GetArrayLength(prevWordCodePointsForBigrams) : 0;
+    int prevWordCodePointsInternal[prevWordCodePointsLength];
+    int *prevWordCodePoints = 0;
+    env->GetIntArrayRegion(xCoordinatesArray, 0, arraySize, xCoordinates);
+    env->GetIntArrayRegion(yCoordinatesArray, 0, arraySize, yCoordinates);
+    env->GetIntArrayRegion(timesArray, 0, arraySize, times);
+    env->GetIntArrayRegion(pointerIdsArray, 0, arraySize, pointerIds);
+    env->GetIntArrayRegion(inputCodePointsArray, 0, inputCodePointsLength, inputCodePoints);
+    if (prevWordCodePointsForBigrams) {
+        env->GetIntArrayRegion(prevWordCodePointsForBigrams, 0, prevWordCodePointsLength,
+                prevWordCodePointsInternal);
+        prevWordCodePoints = prevWordCodePointsInternal;
+    }
+
+    // Output values
+    // TODO: Should be "outputCodePointsLength" and "int outputCodePoints[]"
+    const jsize outputCharsLength = env->GetArrayLength(outputCharsArray);
+    unsigned short outputChars[outputCharsLength];
+    const jsize scoresLength = env->GetArrayLength(scoresArray);
+    int scores[scoresLength];
+    const jsize spaceIndicesLength = env->GetArrayLength(spaceIndicesArray);
+    int spaceIndices[spaceIndicesLength];
+    const jsize outputTypesLength = env->GetArrayLength(outputTypesArray);
+    int outputTypes[outputTypesLength];
+    memset(outputChars, 0, outputCharsLength * sizeof(outputChars[0]));
+    memset(scores, 0, scoresLength * sizeof(scores[0]));
+    memset(spaceIndices, 0, spaceIndicesLength * sizeof(spaceIndices[0]));
+    memset(outputTypes, 0, outputTypesLength * sizeof(outputTypes[0]));
 
     int count;
     if (isGesture || arraySize > 1) {
         count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
-                times, pointerIds, inputCodes, arraySize, prevWordChars, prevWordLength,
-                commitPoint, isGesture, useFullEditDistance, (unsigned short*) outputChars,
-                frequencies, spaceIndices, outputTypes);
+                times, pointerIds, inputCodePoints, arraySize, prevWordCodePoints,
+                prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, outputChars,
+                scores, spaceIndices, outputTypes);
     } else {
-        count = dictionary->getBigrams(prevWordChars, prevWordLength, inputCodes,
-                arraySize, (unsigned short*) outputChars, frequencies, outputTypes);
+        count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength,
+                inputCodePoints, arraySize, outputChars, scores, outputTypes);
     }
 
-    if (prevWordChars) {
-        env->ReleaseIntArrayElements(prevWordForBigrams, prevWordChars, JNI_ABORT);
-    }
-    env->ReleaseIntArrayElements(outputTypesArray, outputTypes, 0);
-    env->ReleaseIntArrayElements(spaceIndexArray, spaceIndices, 0);
-    env->ReleaseCharArrayElements(outputArray, outputChars, 0);
-    env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
-    env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
-    env->ReleaseIntArrayElements(pointerIdArray, pointerIds, 0);
-    env->ReleaseIntArrayElements(timesArray, times, 0);
-    env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0);
-    env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0);
+    // Copy back the output values
+    // TODO: Should be SetIntArrayRegion()
+    env->SetCharArrayRegion(outputCharsArray, 0, outputCharsLength, outputChars);
+    env->SetIntArrayRegion(scoresArray, 0, scoresLength, scores);
+    env->SetIntArrayRegion(spaceIndicesArray, 0, spaceIndicesLength, spaceIndices);
+    env->SetIntArrayRegion(outputTypesArray, 0, outputTypesLength, outputTypes);
+
     return count;
 }
 
 static jint latinime_BinaryDictionary_getFrequency(JNIEnv *env, jobject object, jlong dict,
-        jintArray wordArray, jint wordLength) {
-    Dictionary *dictionary = (Dictionary*)dict;
-    if (!dictionary) return (jboolean) false;
-    jint *word = env->GetIntArrayElements(wordArray, 0);
-    jint result = dictionary->getFrequency(word, wordLength);
-    env->ReleaseIntArrayElements(wordArray, word, JNI_ABORT);
-    return result;
+        jintArray wordArray) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary*>(dict);
+    if (!dictionary) return 0;
+    const jsize codePointLength = env->GetArrayLength(wordArray);
+    int codePoints[codePointLength];
+    env->GetIntArrayRegion(wordArray, 0, codePointLength, codePoints);
+    return dictionary->getFrequency(codePoints, codePointLength);
 }
 
 static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jobject object, jlong dict,
         jintArray wordArray1, jintArray wordArray2) {
-    Dictionary *dictionary = (Dictionary*)dict;
+    Dictionary *dictionary = reinterpret_cast<Dictionary*>(dict);
     if (!dictionary) return (jboolean) false;
-    jint *word1 = env->GetIntArrayElements(wordArray1, 0);
-    jint *word2 = env->GetIntArrayElements(wordArray2, 0);
-    jsize length1 = word1 ? env->GetArrayLength(wordArray1) : 0;
-    jsize length2 = word2 ? env->GetArrayLength(wordArray2) : 0;
-    jboolean result = dictionary->isValidBigram(word1, length1, word2, length2);
-    env->ReleaseIntArrayElements(wordArray2, word2, JNI_ABORT);
-    env->ReleaseIntArrayElements(wordArray1, word1, JNI_ABORT);
-    return result;
+    const jsize codePointLength1 = env->GetArrayLength(wordArray1);
+    const jsize codePointLength2 = env->GetArrayLength(wordArray2);
+    int codePoints1[codePointLength1];
+    int codePoints2[codePointLength2];
+    env->GetIntArrayRegion(wordArray1, 0, codePointLength1, codePoints1);
+    env->GetIntArrayRegion(wordArray2, 0, codePointLength2, codePoints2);
+    return dictionary->isValidBigram(codePoints1, codePointLength1, codePoints2, codePointLength2);
 }
 
 static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jobject object,
-        jcharArray before, jint beforeLength, jcharArray after, jint afterLength, jint score) {
-    jchar *beforeChars = env->GetCharArrayElements(before, 0);
-    jchar *afterChars = env->GetCharArrayElements(after, 0);
-    jfloat result = Correction::RankingAlgorithm::calcNormalizedScore((unsigned short*)beforeChars,
-            beforeLength, (unsigned short*)afterChars, afterLength, score);
-    env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
-    env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
-    return result;
+        jcharArray before, jcharArray after, jint score) {
+    jsize beforeLength = env->GetArrayLength(before);
+    jsize afterLength = env->GetArrayLength(after);
+    jchar beforeChars[beforeLength];
+    jchar afterChars[afterLength];
+    env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
+    env->GetCharArrayRegion(after, 0, afterLength, afterChars);
+    return Correction::RankingAlgorithm::calcNormalizedScore(
+            reinterpret_cast<unsigned short *>(beforeChars), beforeLength,
+            reinterpret_cast<unsigned short *>(afterChars), afterLength, score);
 }
 
 static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object,
-        jcharArray before, jint beforeLength, jcharArray after, jint afterLength) {
-    jchar *beforeChars = env->GetCharArrayElements(before, 0);
-    jchar *afterChars = env->GetCharArrayElements(after, 0);
-    jint result = Correction::RankingAlgorithm::editDistance(
-            (unsigned short*)beforeChars, beforeLength, (unsigned short*)afterChars, afterLength);
-    env->ReleaseCharArrayElements(after, afterChars, JNI_ABORT);
-    env->ReleaseCharArrayElements(before, beforeChars, JNI_ABORT);
-    return result;
+        jcharArray before, jcharArray after) {
+    jsize beforeLength = env->GetArrayLength(before);
+    jsize afterLength = env->GetArrayLength(after);
+    jchar beforeChars[beforeLength];
+    jchar afterChars[afterLength];
+    env->GetCharArrayRegion(before, 0, beforeLength, beforeChars);
+    env->GetCharArrayRegion(after, 0, afterLength, afterChars);
+    return Correction::RankingAlgorithm::editDistance(
+            reinterpret_cast<unsigned short *>(beforeChars), beforeLength,
+            reinterpret_cast<unsigned short *>(afterChars), afterLength);
 }
 
 static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) {
-    Dictionary *dictionary = (Dictionary*)dict;
+    Dictionary *dictionary = reinterpret_cast<Dictionary*>(dict);
     if (!dictionary) return;
     void *dictBuf = dictionary->getDict();
     if (!dictBuf) return;
 #ifdef USE_MMAP_FOR_DICTIONARY
-    releaseDictBuf((void *)((char *)dictBuf - dictionary->getDictBufAdjust()),
+    releaseDictBuf(
+            reinterpret_cast<void *>(
+                    reinterpret_cast<char *>(dictBuf) - dictionary->getDictBufAdjust()),
             dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
 #else // USE_MMAP_FOR_DICTIONARY
     releaseDictBuf(dictBuf, 0, 0);
@@ -247,15 +278,19 @@
 }
 
 static JNINativeMethod sMethods[] = {
-    {"openNative", "(Ljava/lang/String;JJIIIII)J", (void*)latinime_BinaryDictionary_open},
-    {"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
+    {"openNative", "(Ljava/lang/String;JJIIIII)J",
+            reinterpret_cast<void *>(latinime_BinaryDictionary_open)},
+    {"closeNative", "(J)V", reinterpret_cast<void *>(latinime_BinaryDictionary_close)},
     {"getSuggestionsNative", "(JJJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I",
-            (void*) latinime_BinaryDictionary_getSuggestions},
-    {"getFrequencyNative", "(J[II)I", (void*)latinime_BinaryDictionary_getFrequency},
-    {"isValidBigramNative", "(J[I[I)Z", (void*)latinime_BinaryDictionary_isValidBigram},
-    {"calcNormalizedScoreNative", "([CI[CII)F",
-            (void*)latinime_BinaryDictionary_calcNormalizedScore},
-    {"editDistanceNative", "([CI[CI)I", (void*)latinime_BinaryDictionary_editDistance}
+            reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)},
+    {"getFrequencyNative", "(J[I)I",
+            reinterpret_cast<void *>(latinime_BinaryDictionary_getFrequency)},
+    {"isValidBigramNative", "(J[I[I)Z",
+            reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)},
+    {"calcNormalizedScoreNative", "([C[CI)F",
+            reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)},
+    {"editDistanceNative", "([C[C)I",
+            reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)}
 };
 
 int register_BinaryDictionary(JNIEnv *env) {
diff --git a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
index 0f5c396..947360e 100644
--- a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
@@ -17,37 +17,42 @@
 #define LOG_TAG "LatinIME: jni: Session"
 
 #include "com_android_inputmethod_latin_DicTraverseSession.h"
+#include "dic_traverse_wrapper.h"
 #include "jni.h"
 #include "jni_common.h"
 
 namespace latinime {
-void *(*DicTraverseWrapper::sDicTraverseSessionFactoryMethod)() = 0;
-void (*DicTraverseWrapper::sDicTraverseSessionReleaseMethod)(void *) = 0;
-void (*DicTraverseWrapper::sDicTraverseSessionInitMethod)(
-        JNIEnv *, void *, const jintArray, const jint) = 0;
-
-static jlong latinime_setDicTraverseSession(JNIEnv *env, jobject object,
-        jstring localejStr) {
-    void *traverseSession = DicTraverseWrapper::getDicTraverseSession();
+class Dictionary;
+static jlong latinime_setDicTraverseSession(JNIEnv *env, jobject object, jstring localeJStr) {
+    void *traverseSession = DicTraverseWrapper::getDicTraverseSession(env, localeJStr);
     return reinterpret_cast<jlong>(traverseSession);
 }
 
-static void latinime_initDicTraverseSession(JNIEnv *env, jlong traverseSession,
-        jintArray previousWord, jint previousWordLength) {
+static void latinime_initDicTraverseSession(JNIEnv *env, jobject object, jlong traverseSession,
+        jlong dictionary, jintArray previousWord, jint previousWordLength) {
     void *ts = reinterpret_cast<void*>(traverseSession);
-    DicTraverseWrapper::initDicTraverseSession(env, ts, previousWord, previousWordLength);
+    Dictionary *dict = reinterpret_cast<Dictionary*>(dictionary);
+    if (!previousWord) {
+        DicTraverseWrapper::initDicTraverseSession(ts, dict, 0, 0);
+        return;
+    }
+    int prevWord[previousWordLength];
+    env->GetIntArrayRegion(previousWord, 0, previousWordLength, prevWord);
+    DicTraverseWrapper::initDicTraverseSession(ts, dict, prevWord, previousWordLength);
 }
 
-static void latinime_DicTraverseSession_release(
-        JNIEnv *env, jobject object, jlong traverseSession) {
+static void latinime_releaseDicTraverseSession(JNIEnv *env, jobject object, jlong traverseSession) {
     void *ts = reinterpret_cast<void*>(traverseSession);
     DicTraverseWrapper::releaseDicTraverseSession(ts);
 }
 
 static JNINativeMethod sMethods[] = {
-    {"setDicTraverseSessionNative", "(Ljava/lang/String;)J", (void*)latinime_setDicTraverseSession},
-    {"initDicTraverseSessionNative", "(J[II)V", (void*)latinime_initDicTraverseSession},
-    {"releaseDicTraverseSessionNative", "(J)V", (void*)latinime_DicTraverseSession_release}
+    {"setDicTraverseSessionNative", "(Ljava/lang/String;)J",
+            reinterpret_cast<void *>(latinime_setDicTraverseSession)},
+    {"initDicTraverseSessionNative", "(JJ[II)V",
+            reinterpret_cast<void *>(latinime_initDicTraverseSession)},
+    {"releaseDicTraverseSessionNative", "(J)V",
+            reinterpret_cast<void *>(latinime_releaseDicTraverseSession)}
 };
 
 int register_DicTraverseSession(JNIEnv *env) {
diff --git a/native/jni/com_android_inputmethod_latin_DicTraverseSession.h b/native/jni/com_android_inputmethod_latin_DicTraverseSession.h
index a76815d..37531e9 100644
--- a/native/jni/com_android_inputmethod_latin_DicTraverseSession.h
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.h
@@ -21,33 +21,6 @@
 #include "jni.h"
 
 namespace latinime {
-
-// TODO: Remove
-class DicTraverseWrapper {
- public:
-    static void *getDicTraverseSession() {
-        if (sDicTraverseSessionFactoryMethod) {
-            return sDicTraverseSessionFactoryMethod();
-        }
-        return 0;
-    }
-    static void initDicTraverseSession(JNIEnv *env, void *traverseSession,
-            const jintArray prevWord, const jint prevWordLength) {
-        if (sDicTraverseSessionInitMethod) {
-            sDicTraverseSessionInitMethod(env, traverseSession, prevWord, prevWordLength);
-        }
-    }
-    static void releaseDicTraverseSession(void *traverseSession) {
-        if (sDicTraverseSessionReleaseMethod) {
-            sDicTraverseSessionReleaseMethod(traverseSession);
-        }
-    }
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseWrapper);
-    static void *(*sDicTraverseSessionFactoryMethod)();
-    static void (*sDicTraverseSessionInitMethod)(JNIEnv *, void *, const jintArray, const jint);
-    static void (*sDicTraverseSessionReleaseMethod)(void *);
-};
 int register_DicTraverseSession(JNIEnv *env);
 } // namespace latinime
 #endif // _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.cpp b/native/jni/com_android_inputmethod_latin_NativeUtils.cpp
deleted file mode 100644
index 8f1afbe..0000000
--- a/native/jni/com_android_inputmethod_latin_NativeUtils.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 "com_android_inputmethod_latin_NativeUtils.h"
-#include "jni.h"
-#include "jni_common.h"
-
-#include <cmath>
-
-namespace latinime {
-
-static float latinime_NativeUtils_powf(float x, float y) {
-    return powf(x, y);
-}
-
-static JNINativeMethod sMethods[] = {
-    {"powf", "(FF)F", (void*)latinime_NativeUtils_powf}
-};
-
-int register_NativeUtils(JNIEnv *env) {
-    const char *const kClassPathName = "com/android/inputmethod/latin/NativeUtils";
-    return registerNativeMethods(env, kClassPathName, sMethods,
-            sizeof(sMethods) / sizeof(sMethods[0]));
-}
-} // namespace latinime
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index 795262a..1586f25 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -21,7 +21,6 @@
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
 #include "com_android_inputmethod_latin_DicTraverseSession.h"
-#include "com_android_inputmethod_latin_NativeUtils.h"
 #include "defines.h"
 #include "jni.h"
 #include "jni_common.h"
@@ -56,11 +55,6 @@
         goto bail;
     }
 
-    if (!register_NativeUtils(env)) {
-        AKLOGE("ERROR: NativeUtils native registration failed");
-        goto bail;
-    }
-
     /* success -- return valid version number */
     result = JNI_VERSION_1_6;
 
diff --git a/native/jni/src/bigram_dictionary.cpp b/native/jni/src/bigram_dictionary.cpp
index 2201711..df1ebc0 100644
--- a/native/jni/src/bigram_dictionary.cpp
+++ b/native/jni/src/bigram_dictionary.cpp
@@ -60,14 +60,15 @@
         AKLOGI("Bigram: InsertAt -> %d MAX_PREDICTIONS: %d", insertAt, MAX_PREDICTIONS);
     }
     if (insertAt < MAX_PREDICTIONS) {
-        memmove((char*) bigramFreq + (insertAt + 1) * sizeof(bigramFreq[0]),
-               (char*) bigramFreq + insertAt * sizeof(bigramFreq[0]),
-               (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
+        memmove(reinterpret_cast<char *>(bigramFreq) + (insertAt + 1) * sizeof(bigramFreq[0]),
+                reinterpret_cast<char *>(bigramFreq) + insertAt * sizeof(bigramFreq[0]),
+                (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0]));
         bigramFreq[insertAt] = frequency;
         outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
-        memmove((char*) bigramChars + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
-               (char*) bigramChars + (insertAt    ) * MAX_WORD_LENGTH * sizeof(short),
-               (MAX_PREDICTIONS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
+        memmove(reinterpret_cast<char *>(bigramChars)
+                + (insertAt + 1) * MAX_WORD_LENGTH * sizeof(short),
+                reinterpret_cast<char *>(bigramChars) + insertAt * MAX_WORD_LENGTH * sizeof(short),
+                (MAX_PREDICTIONS - insertAt - 1) * sizeof(short) * MAX_WORD_LENGTH);
         unsigned short *dest = bigramChars + (insertAt    ) * MAX_WORD_LENGTH;
         while (length--) {
             *dest++ = *word++;
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 2ee4077..4cabc84 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -52,6 +52,8 @@
 
     // Mask for attribute frequency, stored on 4 bits inside the flags byte.
     static const int MASK_ATTRIBUTE_FREQUENCY = 0x0F;
+    // The numeric value of the shortcut frequency that means 'whitelist'.
+    static const int WHITELIST_SHORTCUT_FREQUENCY = 15;
 
     // Mask and flags for attribute address type selection.
     static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
@@ -99,6 +101,7 @@
     static bool hasChildrenInFlags(const uint8_t flags);
     static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags,
             int *pos);
+    static int getAttributeFrequencyFromFlags(const int flags);
     static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord,
             const int length, const bool forceLowerCaseSearch);
     static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth,
@@ -340,6 +343,10 @@
     }
 }
 
+inline int BinaryFormat::getAttributeFrequencyFromFlags(const int flags) {
+    return flags & MASK_ATTRIBUTE_FREQUENCY;
+}
+
 // This function gets the byte position of the last chargroup of the exact matching word in the
 // dictionary. If no match is found, it returns NOT_VALID_WORD.
 inline int BinaryFormat::getTerminalPosition(const uint8_t *const root,
diff --git a/native/jni/src/char_utils.cpp b/native/jni/src/char_utils.cpp
index 45d49b0..fc0a059 100644
--- a/native/jni/src/char_utils.cpp
+++ b/native/jni/src/char_utils.cpp
@@ -885,16 +885,17 @@
 };
 
 static int compare_pair_capital(const void *a, const void *b) {
-    return (int)(*(unsigned short *)a)
-            - (int)((struct LatinCapitalSmallPair*)b)->capital;
+    return static_cast<int>(*reinterpret_cast<const unsigned short *>(a))
+            - static_cast<int>(
+                    (reinterpret_cast<const struct LatinCapitalSmallPair *>(b))->capital);
 }
 
 unsigned short latin_tolower(unsigned short c) {
     struct LatinCapitalSmallPair *p =
-            (struct LatinCapitalSmallPair *)bsearch(&c, SORTED_CHAR_MAP,
+            reinterpret_cast<struct LatinCapitalSmallPair *>(bsearch(&c, SORTED_CHAR_MAP,
                     sizeof(SORTED_CHAR_MAP) / sizeof(SORTED_CHAR_MAP[0]),
                     sizeof(SORTED_CHAR_MAP[0]),
-                    compare_pair_capital);
+                    compare_pair_capital));
     return p ? p->small : c;
 }
 } // namespace latinime
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index ea4bdda..e55da01 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -154,11 +154,13 @@
         if (mSkipPos >= 0) ++inputCount;
         if (mExcessivePos >= 0) ++inputCount;
         if (mTransposedPos >= 0) ++inputCount;
-        // TODO: remove this assert
-        assert(inputCount <= 1);
     }
 }
 
+bool Correction::sameAsTyped() {
+    return mProximityInfoState.sameAsTyped(mWord, mOutputIndex);
+}
+
 int Correction::getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
         const int wordCount, const bool isSpaceProximity, const unsigned short *word) {
     return Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(freqArray, wordLengthArray,
@@ -1094,7 +1096,7 @@
 // In dictionary.cpp, getSuggestion() method,
 // suggestion scores are computed using the below formula.
 // original score
-//  := pow(mTypedLetterMultiplier (this is defined 2),
+//  := powf(mTypedLetterMultiplier (this is defined 2),
 //         (the number of matched characters between typed word and suggested word))
 //     * (individual word's score which defined in the unigram dictionary,
 //         and this score is defined in range [0, 255].)
@@ -1106,11 +1108,11 @@
 //       capitalization, then treat it as if the score was 255.
 //     - If before.length() == after.length()
 //       => multiply by mFullWordMultiplier (this is defined 2))
-// So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
+// So, maximum original score is powf(2, min(before.length(), after.length())) * 255 * 2 * 1.2
 // For historical reasons we ignore the 1.2 modifier (because the measure for a good
 // autocorrection threshold was done at a time when it didn't exist). This doesn't change
 // the result.
-// So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2.
+// So, we can normalize original score by dividing powf(2, min(b.l(),a.l())) * 255 * 2.
 
 /* static */
 float Correction::RankingAlgorithm::calcNormalizedScore(const unsigned short *before,
@@ -1132,7 +1134,7 @@
     }
 
     const float maxScore = score >= S_INT_MAX ? S_INT_MAX : MAX_INITIAL_SCORE
-            * pow(static_cast<float>(TYPED_LETTER_MULTIPLIER),
+            * powf(static_cast<float>(TYPED_LETTER_MULTIPLIER),
                     static_cast<float>(min(beforeLength, afterLength - spaceCount)))
             * FULL_WORD_MULTIPLIER;
 
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index 81623a4..57e7b71 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -105,6 +105,7 @@
             const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance,
             const bool doAutoCompletion, const int maxErrors);
     void checkState();
+    bool sameAsTyped();
     bool initProcessState(const int index);
 
     int getInputIndex();
diff --git a/native/jni/src/debug.h b/native/jni/src/debug.h
index 2168d66..2432b1f 100644
--- a/native/jni/src/debug.h
+++ b/native/jni/src/debug.h
@@ -58,11 +58,12 @@
 }
 
 static inline void printDebug(const char *tag, int *codes, int codesSize, int MAX_PROXIMITY_CHARS) {
-    unsigned char *buf = (unsigned char*)malloc((1 + codesSize) * sizeof(*buf));
+    unsigned char *buf = reinterpret_cast<unsigned char *>(malloc((1 + codesSize) * sizeof(*buf)));
 
     buf[codesSize] = 0;
-    while (--codesSize >= 0)
-        buf[codesSize] = (unsigned char)codes[codesSize * MAX_PROXIMITY_CHARS];
+    while (--codesSize >= 0) {
+        buf[codesSize] = static_cast<unsigned char>(codes[codesSize * MAX_PROXIMITY_CHARS]);
+    }
     AKLOGI("%s, WORD = %s", tag, buf);
 
     free(buf);
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.h b/native/jni/src/dic_traverse_wrapper.cpp
similarity index 64%
rename from native/jni/com_android_inputmethod_latin_NativeUtils.h
rename to native/jni/src/dic_traverse_wrapper.cpp
index d1ffb8f..1f7dcbf 100644
--- a/native/jni/com_android_inputmethod_latin_NativeUtils.h
+++ b/native/jni/src/dic_traverse_wrapper.cpp
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
-#define _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
+#define LOG_TAG "LatinIME: jni: Session"
 
-#include "jni.h"
+#include "dic_traverse_wrapper.h"
 
 namespace latinime {
-
-int register_NativeUtils(JNIEnv *env);
-
+void *(*DicTraverseWrapper::sDicTraverseSessionFactoryMethod)(JNIEnv *env, jstring locale) = 0;
+void (*DicTraverseWrapper::sDicTraverseSessionReleaseMethod)(void *) = 0;
+void (*DicTraverseWrapper::sDicTraverseSessionInitMethod)(
+        void *, Dictionary *, const int *, const int) = 0;
 } // namespace latinime
-#endif // _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
diff --git a/native/jni/src/dic_traverse_wrapper.h b/native/jni/src/dic_traverse_wrapper.h
new file mode 100644
index 0000000..8396d00
--- /dev/null
+++ b/native/jni/src/dic_traverse_wrapper.h
@@ -0,0 +1,66 @@
+/*
+ * 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_DIC_TRAVERSE_WRAPPER_H
+#define LATINIME_DIC_TRAVERSE_WRAPPER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "jni.h"
+
+namespace latinime {
+class Dictionary;
+// TODO: Remove
+class DicTraverseWrapper {
+ public:
+    static void *getDicTraverseSession(JNIEnv *env, jstring locale) {
+        if (sDicTraverseSessionFactoryMethod) {
+            return sDicTraverseSessionFactoryMethod(env, locale);
+        }
+        return 0;
+    }
+    static void initDicTraverseSession(void *traverseSession,
+            Dictionary *dictionary, const int *prevWord, const int prevWordLength) {
+        if (sDicTraverseSessionInitMethod) {
+            sDicTraverseSessionInitMethod(traverseSession, dictionary, prevWord, prevWordLength);
+        }
+    }
+    static void releaseDicTraverseSession(void *traverseSession) {
+        if (sDicTraverseSessionReleaseMethod) {
+            sDicTraverseSessionReleaseMethod(traverseSession);
+        }
+    }
+    static void setTraverseSessionFactoryMethod(
+            void *(*factoryMethod)(JNIEnv *env, jstring locale)) {
+        sDicTraverseSessionFactoryMethod = factoryMethod;
+    }
+    static void setTraverseSessionInitMethod(
+            void (*initMethod)(void *, Dictionary *, const int *, const int)) {
+        sDicTraverseSessionInitMethod = initMethod;
+    }
+    static void setTraverseSessionReleaseMethod(void (*releaseMethod)(void *)) {
+        sDicTraverseSessionReleaseMethod = releaseMethod;
+    }
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseWrapper);
+    static void *(*sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring);
+    static void (*sDicTraverseSessionInitMethod)(void *, Dictionary *, const int *, const int);
+    static void (*sDicTraverseSessionReleaseMethod)(void *);
+};
+int register_DicTraverseSession(JNIEnv *env);
+} // namespace latinime
+#endif // LATINIME_DIC_TRAVERSE_WRAPPER_H
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index 8c785a2..9e4bd15 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -22,6 +22,7 @@
 #include "binary_format.h"
 #include "defines.h"
 #include "dictionary.h"
+#include "dic_traverse_wrapper.h"
 #include "gesture_decoder_wrapper.h"
 #include "unigram_dictionary.h"
 
@@ -31,8 +32,9 @@
 Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust,
         int typedLetterMultiplier, int fullWordMultiplier,
         int maxWordLength, int maxWords, int maxPredictions)
-    : mDict((unsigned char*) dict), mDictSize(dictSize),
-      mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust) {
+    : mDict(reinterpret_cast<unsigned char *>(dict)),
+      mOffsetDict((reinterpret_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)),
+      mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust) {
     if (DEBUG_DICT) {
         if (MAX_WORD_LENGTH_INTERNAL < maxWordLength) {
             AKLOGI("Max word length (%d) is greater than %d",
@@ -40,14 +42,11 @@
             AKLOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
         }
     }
-    const unsigned int headerSize = BinaryFormat::getHeaderSize(mDict);
     const unsigned int options = BinaryFormat::getFlags(mDict);
-    mUnigramDictionary = new UnigramDictionary(mDict + headerSize, typedLetterMultiplier,
+    mUnigramDictionary = new UnigramDictionary(mOffsetDict, typedLetterMultiplier,
             fullWordMultiplier, maxWordLength, maxWords, options);
-    mBigramDictionary = new BigramDictionary(mDict + headerSize, maxWordLength, maxPredictions);
+    mBigramDictionary = new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions);
     mGestureDecoder = new GestureDecoderWrapper(maxWordLength, maxWords);
-    mGestureDecoder->setDict(mUnigramDictionary, mBigramDictionary,
-            mDict + headerSize /* dict root */, 0 /* root pos */);
 }
 
 Dictionary::~Dictionary() {
@@ -64,7 +63,8 @@
         int *frequencies, int *spaceIndices, int *outputTypes) {
     int result = 0;
     if (isGesture) {
-        mGestureDecoder->setPrevWord(prevWordChars, prevWordLength);
+        DicTraverseWrapper::initDicTraverseSession(
+                traverseSession, this, prevWordChars, prevWordLength);
         result = mGestureDecoder->getSuggestions(proximityInfo, traverseSession,
                 xcoordinates, ycoordinates, times, pointerIds, codes, codesSize, commitPoint,
                 outWords, frequencies, spaceIndices, outputTypes);
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
index 2c79527..3b55e5d 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/dictionary.h
@@ -55,7 +55,12 @@
 
     int getFrequency(const int32_t *word, int length) const;
     bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
-    void *getDict() const { return (void *)mDict; }
+    void *getDict() const { // required to release dictionary buffer
+        return reinterpret_cast<void *>(const_cast<unsigned char *>(mDict));
+    }
+    void *getOffsetDict() const {
+        return reinterpret_cast<void *>(const_cast<unsigned char *>(mOffsetDict));
+    }
     int getDictSize() const { return mDictSize; }
     int getMmapFd() const { return mMmapFd; }
     int getDictBufAdjust() const { return mDictBufAdjust; }
@@ -68,6 +73,7 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
     const unsigned char *mDict;
+    const unsigned char *mOffsetDict;
 
     // Used only for the mmap version of dictionary loading, but we use these as dummy variables
     // also for the malloc version.
@@ -85,8 +91,9 @@
 inline int Dictionary::wideStrLen(unsigned short *str) {
     if (!str) return 0;
     unsigned short *end = str;
-    while (*end)
+    while (*end) {
         end++;
+    }
     return end - str;
 }
 } // namespace latinime
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
new file mode 100644
index 0000000..deb0425
--- /dev/null
+++ b/native/jni/src/geometry_utils.h
@@ -0,0 +1,103 @@
+/*
+ * 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_GEOMETRY_UTILS_H
+#define LATINIME_GEOMETRY_UTILS_H
+
+#include <cmath>
+
+#define MAX_DISTANCE 10000000
+#define MAX_PATHS 2
+
+#define DEBUG_DECODER false
+
+#define M_PI_F 3.14159265f
+
+namespace latinime {
+
+static inline float squareFloat(float x) {
+    return x * x;
+}
+
+static inline float getNormalizedSquaredDistanceFloat(float x1, float y1, float x2, float y2,
+        float scale) {
+    return squareFloat((x1 - x2) / scale) + squareFloat((y1 - y2) / scale);
+}
+
+static inline float getSquaredDistanceFloat(float x1, float y1, float x2, float y2) {
+    return squareFloat(x1 - x2) + squareFloat(y1 - y2);
+}
+
+static inline float getDistanceFloat(float x1, float y1, float x2, float y2) {
+    return hypotf(x1 - x2, y1 - y2);
+}
+
+static inline int getDistanceInt(int x1, int y1, int x2, int y2) {
+    return static_cast<int>(getDistanceFloat(static_cast<float>(x1), static_cast<float>(y1),
+            static_cast<float>(x2), static_cast<float>(y2)));
+}
+
+static inline float getAngle(int x1, int y1, int x2, int y2) {
+    const int dx = x1 - x2;
+    const int dy = y1 - y2;
+    if (dx == 0 && dy == 0) return 0;
+    return atan2f(static_cast<float>(dy), static_cast<float>(dx));
+}
+
+static inline float getAngleDiff(float a1, float a2) {
+    const float diff = fabsf(a1 - a2);
+    if (diff > M_PI_F) {
+        return 2.0f * M_PI_F - diff;
+    }
+    return diff;
+}
+
+// static float pointToLineSegSquaredDistanceFloat(
+//         float x, float y, float x1, float y1, float x2, float y2) {
+//     float A = x - x1;
+//     float B = y - y1;
+//     float C = x2 - x1;
+//     float D = y2 - y1;
+//     return fabsf(A * D - C * B) / sqrtf(C * C + D * D);
+// }
+
+static inline float pointToLineSegSquaredDistanceFloat(
+        float x, float y, float x1, float y1, float x2, float y2) {
+    const float ray1x = x - x1;
+    const float ray1y = y - y1;
+    const float ray2x = x2 - x1;
+    const float ray2y = y2 - y1;
+
+    const float dotProduct = ray1x * ray2x + ray1y * ray2y;
+    const float lineLengthSqr = squareFloat(ray2x) + squareFloat(ray2y);
+    const float projectionLengthSqr = dotProduct / lineLengthSqr;
+
+    float projectionX;
+    float projectionY;
+    if (projectionLengthSqr < 0.0f) {
+        projectionX = x1;
+        projectionY = y1;
+    } else if (projectionLengthSqr > 1.0f) {
+        projectionX = x2;
+        projectionY = y2;
+    } else {
+        projectionX = x1 + projectionLengthSqr * ray2x;
+        projectionY = y1 + projectionLengthSqr * ray2y;
+    }
+    return getSquaredDistanceFloat(x, y, projectionX, projectionY);
+}
+} // namespace latinime
+#endif // LATINIME_GEOMETRY_UTILS_H
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.h b/native/jni/src/gesture/gesture_decoder_wrapper.h
index b70c8e0..f8bfe7c 100644
--- a/native/jni/src/gesture/gesture_decoder_wrapper.h
+++ b/native/jni/src/gesture/gesture_decoder_wrapper.h
@@ -48,35 +48,13 @@
                 inputSize, commitPoint, outWords, frequencies, outputIndices, outputTypes);
     }
 
-    void reset() {
-        if (!mIncrementalDecoderInterface) {
-            return;
-        }
-        mIncrementalDecoderInterface->reset();
-    }
-
-    void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram,
-            const uint8_t *dictRoot, int rootPos) {
-        if (!mIncrementalDecoderInterface) {
-            return;
-        }
-        mIncrementalDecoderInterface->setDict(dict, bigram, dictRoot, rootPos);
-    }
-
-    void setPrevWord(const int32_t *prevWord, int prevWordLength) {
-        if (!mIncrementalDecoderInterface) {
-            return;
-        }
-        mIncrementalDecoderInterface->setPrevWord(prevWord, prevWordLength);
-    }
-
     static void setGestureDecoderFactoryMethod(
             IncrementalDecoderInterface *(*factoryMethod)(int, int)) {
         sGestureDecoderFactoryMethod = factoryMethod;
     }
 
  private:
-    DISALLOW_COPY_AND_ASSIGN(GestureDecoderWrapper);
+    DISALLOW_IMPLICIT_CONSTRUCTORS(GestureDecoderWrapper);
     static IncrementalDecoderInterface *getGestureDecoderInstance(int maxWordLength, int maxWords) {
         if (sGestureDecoderFactoryMethod) {
             return sGestureDecoderFactoryMethod(maxWordLength, maxWords);
diff --git a/native/jni/src/gesture/incremental_decoder_interface.h b/native/jni/src/gesture/incremental_decoder_interface.h
index e8d3a53..04f0095 100644
--- a/native/jni/src/gesture/incremental_decoder_interface.h
+++ b/native/jni/src/gesture/incremental_decoder_interface.h
@@ -32,11 +32,10 @@
             int *inputXs, int *inputYs, int *times, int *pointerIds, int *codes,
             int inputSize, int commitPoint, unsigned short *outWords, int *frequencies,
             int *outputIndices, int *outputTypes) = 0;
-    virtual void reset() = 0;
-    virtual void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram,
-            const uint8_t *dictRoot, int rootPos) = 0;
-    virtual void setPrevWord(const int32_t *prevWord, int prevWordLength) = 0;
+    IncrementalDecoderInterface() { };
     virtual ~IncrementalDecoderInterface() { };
+ private:
+    DISALLOW_COPY_AND_ASSIGN(IncrementalDecoderInterface);
 };
 } // namespace latinime
 #endif // LATINIME_INCREMENTAL_DECODER_INTERFACE_H
diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.h b/native/jni/src/gesture/incremental_decoder_wrapper.h
index 8d66b4e..5cb2ee3 100644
--- a/native/jni/src/gesture/incremental_decoder_wrapper.h
+++ b/native/jni/src/gesture/incremental_decoder_wrapper.h
@@ -48,35 +48,13 @@
                 inputSize, commitPoint, outWords, frequencies, outputIndices, outputTypes);
     }
 
-    void reset() {
-        if (!mIncrementalDecoderInterface) {
-            return;
-        }
-        mIncrementalDecoderInterface->reset();
-    }
-
-    void setDict(const UnigramDictionary *dict, const BigramDictionary *bigram,
-            const uint8_t *dictRoot, int rootPos) {
-        if (!mIncrementalDecoderInterface) {
-            return;
-        }
-        mIncrementalDecoderInterface->setDict(dict, bigram, dictRoot, rootPos);
-    }
-
-    void setPrevWord(const int32_t *prevWord, int prevWordLength) {
-        if (!mIncrementalDecoderInterface) {
-            return;
-        }
-        mIncrementalDecoderInterface->setPrevWord(prevWord, prevWordLength);
-    }
-
     static void setIncrementalDecoderFactoryMethod(
             IncrementalDecoderInterface *(*factoryMethod)(int, int)) {
         sIncrementalDecoderFactoryMethod = factoryMethod;
     }
 
  private:
-    DISALLOW_COPY_AND_ASSIGN(IncrementalDecoderWrapper);
+    DISALLOW_IMPLICIT_CONSTRUCTORS(IncrementalDecoderWrapper);
     static IncrementalDecoderInterface *getIncrementalDecoderInstance(int maxWordLength,
             int maxWords) {
         if (sIncrementalDecoderFactoryMethod) {
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 2564c8a..ade78a1 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -23,6 +23,7 @@
 #include "additional_proximity_chars.h"
 #include "char_utils.h"
 #include "defines.h"
+#include "geometry_utils.h"
 #include "jni.h"
 #include "proximity_info.h"
 
@@ -33,7 +34,7 @@
     if (jArray && buffer) {
         env->GetIntArrayRegion(jArray, 0, len, buffer);
     } else if (buffer) {
-        memset(buffer, 0, len);
+        memset(buffer, 0, len * sizeof(jint));
     }
 }
 
@@ -42,7 +43,7 @@
     if (jArray && buffer) {
         env->GetFloatArrayRegion(jArray, 0, len, buffer);
     } else if (buffer) {
-        memset(buffer, 0, len);
+        memset(buffer, 0, len * sizeof(jfloat));
     }
 }
 
@@ -55,6 +56,7 @@
         const jfloatArray sweetSpotRadii)
         : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth),
           KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight),
+          MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
           MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
           CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
           CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
@@ -84,6 +86,7 @@
     safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterYs, KEY_COUNT, mSweetSpotCenterYs);
     safeGetOrFillZeroFloatArrayRegion(env, sweetSpotRadii, KEY_COUNT, mSweetSpotRadii);
     initializeCodeToKeyIndex();
+    initializeG();
 }
 
 // Build the reversed look up table from the char code to the index in mKeyXCoordinates,
@@ -222,11 +225,72 @@
     return mCodeToKeyIndex[baseLowerC];
 }
 
+int ProximityInfo::getKeyCode(const int keyIndex) const {
+    if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
+        return NOT_AN_INDEX;
+    }
+    return mKeyToCodeIndexG[keyIndex];
+}
+
+void ProximityInfo::initializeG() {
+    for (int i = 0; i < KEY_COUNT; ++i) {
+        const int code = mKeyCharCodes[i];
+        const int lowerCode = toBaseLowerCase(code);
+        mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
+        mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
+        if (code != lowerCode && lowerCode >= 0 && lowerCode <= MAX_CHAR_CODE) {
+            mCodeToKeyIndex[lowerCode] = i;
+            mKeyToCodeIndexG[i] = lowerCode;
+        } else {
+            mKeyToCodeIndexG[i] = code;
+        }
+    }
+    for (int i = 0; i < KEY_COUNT; i++) {
+        mKeyKeyDistancesG[i][i] = 0;
+        for (int j = i + 1; j < KEY_COUNT; j++) {
+            mKeyKeyDistancesG[i][j] = getDistanceInt(
+                    mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
+            mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
+        }
+    }
+}
+
+float ProximityInfo::getKeyCenterXOfCharG(int charCode) const {
+    return getKeyCenterXOfIdG(getKeyIndex(charCode));
+}
+
+float ProximityInfo::getKeyCenterYOfCharG(int charCode) const {
+    return getKeyCenterYOfIdG(getKeyIndex(charCode));
+}
+
+float ProximityInfo::getKeyCenterXOfIdG(int keyId) const {
+    if (keyId >= 0) {
+        return mCenterXsG[keyId];
+    }
+    return 0;
+}
+
+float ProximityInfo::getKeyCenterYOfIdG(int keyId) const {
+    if (keyId >= 0) {
+        return mCenterYsG[keyId];
+    }
+    return 0;
+}
+
+int ProximityInfo::getKeyKeyDistanceG(int key0, int key1) const {
+    const int keyId0 = getKeyIndex(key0);
+    const int keyId1 = getKeyIndex(key1);
+    if (keyId0 >= 0 && keyId1 >= 0) {
+        return mKeyKeyDistancesG[keyId0][keyId1];
+    }
+    return 0;
+}
+
 // TODO: [Staging] Optimize
 void ProximityInfo::getCenters(int *centerXs, int *centerYs, int *codeToKeyIndex,
         int *keyToCodeIndex, int *keyCount, int *keyWidth) const {
     *keyCount = KEY_COUNT;
-    *keyWidth = sqrt(static_cast<float>(MOST_COMMON_KEY_WIDTH_SQUARE));
+    *keyWidth = sqrtf(static_cast<float>(MOST_COMMON_KEY_WIDTH_SQUARE));
 
     for (int i = 0; i < KEY_COUNT; ++i) {
         const int code = mKeyCharCodes[i];
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index d7e24c5..58f2d75 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -49,6 +49,7 @@
         return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
     }
     int getKeyIndex(const int c) const;
+    int getKeyCode(const int keyIndex) const;
     bool hasSweetSpotData(const int keyIndex) const {
         // When there are no calibration data for a key,
         // the radius of the key is assigned to zero.
@@ -70,6 +71,10 @@
         return HAS_TOUCH_POSITION_CORRECTION_DATA;
     }
 
+    int getMostCommonKeyWidth() const {
+        return MOST_COMMON_KEY_WIDTH;
+    }
+
     int getMostCommonKeyWidthSquare() const {
         return MOST_COMMON_KEY_WIDTH_SQUARE;
     }
@@ -98,6 +103,12 @@
         return GRID_HEIGHT;
     }
 
+    float getKeyCenterXOfCharG(int charCode) const;
+    float getKeyCenterYOfCharG(int charCode) const;
+    float getKeyCenterXOfIdG(int keyId) const;
+    float getKeyCenterYOfIdG(int keyId) const;
+    int getKeyKeyDistanceG(int key0, int key1) const;
+
     // Returns the keyboard key-center information.
     void getCenters(int *centersX, int *centersY, int *codeToKeyIndex, int *keyToCodeIndex,
             int *keyCount, int *keyWidth) const;
@@ -113,6 +124,7 @@
 
     int getStartIndexFromCoordinates(const int x, const int y) const;
     void initializeCodeToKeyIndex();
+    void initializeG();
     float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const;
     float calculateSquaredDistanceFromSweetSpotCenter(
             const int keyIndex, const int inputIndex) const;
@@ -123,6 +135,7 @@
     const int KEYBOARD_HEIGHT;
     const int GRID_WIDTH;
     const int GRID_HEIGHT;
+    const int MOST_COMMON_KEY_WIDTH;
     const int MOST_COMMON_KEY_WIDTH_SQUARE;
     const int CELL_WIDTH;
     const int CELL_HEIGHT;
@@ -139,6 +152,11 @@
     float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD];
     int mCodeToKeyIndex[MAX_CHAR_CODE + 1];
+
+    int mKeyToCodeIndexG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mCenterXsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mCenterYsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mKeyKeyDistancesG[MAX_KEY_COUNT_IN_A_KEYBOARD][MAX_KEY_COUNT_IN_A_KEYBOARD];
     // TODO: move to correction.h
 };
 } // namespace latinime
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index 76d4551..474c407 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -160,6 +160,21 @@
         return mTouchPositionCorrectionEnabled;
     }
 
+    inline bool sameAsTyped(const unsigned short *word, int length) const {
+        if (length != mInputLength) {
+            return false;
+        }
+        const int *inputCodes = mInputCodes;
+        while (length--) {
+            if (static_cast<unsigned int>(*inputCodes) != static_cast<unsigned int>(*word)) {
+                return false;
+            }
+            inputCodes += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
+            word++;
+        }
+        return true;
+    }
+
  private:
     DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
     /////////////////////////////////////////
@@ -179,21 +194,6 @@
         return mInputXCoordinates && mInputYCoordinates;
     }
 
-    bool sameAsTyped(const unsigned short *word, int length) const {
-        if (length != mInputLength) {
-            return false;
-        }
-        const int *inputCodes = mInputCodes;
-        while (length--) {
-            if ((unsigned int) *inputCodes != (unsigned int) *word) {
-                return false;
-            }
-            inputCodes += MAX_PROXIMITY_CHARS_SIZE_INTERNAL;
-            word++;
-        }
-        return true;
-    }
-
     // const
     const ProximityInfo *mProximityInfo;
     bool mHasTouchPositionCorrectionData;
diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h
index d633645..1ae9c7c 100644
--- a/native/jni/src/terminal_attributes.h
+++ b/native/jni/src/terminal_attributes.h
@@ -46,7 +46,7 @@
         // Gets the shortcut target itself as a uint16_t string. For parameters and return value
         // see BinaryFormat::getWordAtAddress.
         // TODO: make the output an uint32_t* to handle the whole unicode range.
-        inline int getNextShortcutTarget(const int maxDepth, uint16_t *outWord) {
+        inline int getNextShortcutTarget(const int maxDepth, uint16_t *outWord, int *outFreq) {
             const int shortcutFlags = BinaryFormat::getFlagsAndForwardPointer(mDict, &mPos);
             mHasNextShortcutTarget =
                     0 != (shortcutFlags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT);
@@ -56,6 +56,7 @@
                 if (NOT_A_CHARACTER == charCode) break;
                 outWord[i] = (uint16_t)charCode;
             }
+            *outFreq = BinaryFormat::getAttributeFrequencyFromFlags(shortcutFlags);
             mPos += BinaryFormat::CHARACTER_ARRAY_TERMINATOR_SIZE;
             return i;
         }
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index b6b0210..cc6d39a 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -63,8 +63,8 @@
 
 // TODO: This needs to take a const unsigned short* and not tinker with its contents
 static inline void addWord(
-        unsigned short *word, int length, int frequency, WordsPriorityQueue *queue) {
-    queue->push(frequency, word, length);
+        unsigned short *word, int length, int frequency, WordsPriorityQueue *queue, int type) {
+    queue->push(frequency, word, length, type);
 }
 
 // Return the replacement code point for a digraph, or 0 if none.
@@ -213,8 +213,8 @@
         AKLOGI("Max normalized score = %f", ns);
     }
     const int suggestedWordsCount =
-            queuePool.getMasterQueue()->outputSuggestions(
-                    masterCorrection.getPrimaryInputWord(), codesSize, frequencies, outWords);
+            queuePool.getMasterQueue()->outputSuggestions(masterCorrection.getPrimaryInputWord(),
+                    codesSize, frequencies, outWords, outputTypes);
 
     if (DEBUG_DICT) {
         float ns = queuePool.getMasterQueue()->getHighestNormalizedScore(
@@ -391,7 +391,12 @@
         const int finalProbability =
                 correction->getFinalProbability(probability, &wordPointer, &wordLength);
         if (finalProbability != NOT_A_PROBABILITY) {
-            addWord(wordPointer, wordLength, finalProbability, masterQueue);
+            if (0 != finalProbability) {
+                // If the probability is 0, we don't want to add this word. However we still
+                // want to add its shortcuts (including a possible whitelist entry) if any.
+                addWord(wordPointer, wordLength, finalProbability, masterQueue,
+                        Dictionary::KIND_CORRECTION);
+            }
 
             const int shortcutProbability = finalProbability > 0 ? finalProbability - 1 : 0;
             // Please note that the shortcut candidates will be added to the master queue only.
@@ -406,10 +411,21 @@
                 // with the same score. For the moment we use -1 to make sure the shortcut will
                 // never be in front of the word.
                 uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL];
+                int shortcutFrequency;
                 const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
-                        MAX_WORD_LENGTH_INTERNAL, shortcutTarget);
-                addWord(shortcutTarget, shortcutTargetStringLength, shortcutProbability,
-                        masterQueue);
+                        MAX_WORD_LENGTH_INTERNAL, shortcutTarget, &shortcutFrequency);
+                int shortcutScore;
+                int kind;
+                if (shortcutFrequency == BinaryFormat::WHITELIST_SHORTCUT_FREQUENCY
+                        && correction->sameAsTyped()) {
+                    shortcutScore = S_INT_MAX;
+                    kind = Dictionary::KIND_WHITELIST;
+                } else {
+                    shortcutScore = shortcutProbability;
+                    kind = Dictionary::KIND_CORRECTION;
+                }
+                addWord(shortcutTarget, shortcutTargetStringLength, shortcutScore,
+                        masterQueue, kind);
             }
         }
     }
@@ -424,7 +440,7 @@
         }
         const int finalProbability = correction->getFinalProbabilityForSubQueue(
                 probability, &wordPointer, &wordLength, inputIndex);
-        addWord(wordPointer, wordLength, finalProbability, subQueue);
+        addWord(wordPointer, wordLength, finalProbability, subQueue, Dictionary::KIND_CORRECTION);
     }
 }
 
@@ -572,7 +588,8 @@
             AKLOGI("Split two words: freq = %d, length = %d, %d, isSpace ? %d", pairFreq,
                     inputLength, tempOutputWordLength, isSpaceProximity);
         }
-        addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue());
+        addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue(),
+                Dictionary::KIND_CORRECTION);
     }
     return FLAG_MULTIPLE_SUGGEST_CONTINUE;
 }
diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h
index c0dedb5..e97e16a 100644
--- a/native/jni/src/words_priority_queue.h
+++ b/native/jni/src/words_priority_queue.h
@@ -33,12 +33,14 @@
         unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
         int mWordLength;
         bool mUsed;
+        int mType;
 
-        void setParams(int score, unsigned short *word, int wordLength) {
+        void setParams(int score, unsigned short *word, int wordLength, int type) {
             mScore = score;
             mWordLength = wordLength;
             memcpy(mWord, word, sizeof(unsigned short) * wordLength);
             mUsed = true;
+            mType = type;
         }
     };
 
@@ -56,7 +58,7 @@
         delete[] mSuggestedWords;
     }
 
-    void push(int score, unsigned short *word, int wordLength) {
+    void push(int score, unsigned short *word, int wordLength, int type) {
         SuggestedWord *sw = 0;
         if (mSuggestions.size() >= MAX_WORDS) {
             sw = mSuggestions.top();
@@ -69,9 +71,9 @@
             }
         }
         if (sw == 0) {
-            sw = getFreeSuggestedWord(score, word, wordLength);
+            sw = getFreeSuggestedWord(score, word, wordLength, type);
         } else {
-            sw->setParams(score, word, wordLength);
+            sw->setParams(score, word, wordLength, type);
         }
         if (sw == 0) {
             AKLOGE("SuggestedWord is accidentally null.");
@@ -94,7 +96,7 @@
     }
 
     int outputSuggestions(const unsigned short *before, const int beforeLength,
-            int *frequencies, unsigned short *outputChars) {
+            int *frequencies, unsigned short *outputChars, int* outputTypes) {
         mHighestSuggestedWord = 0;
         const unsigned int size = min(
               MAX_WORDS, static_cast<unsigned int>(mSuggestions.size()));
@@ -127,7 +129,7 @@
                 }
             }
             if (maxIndex > 0 && nsMaxSw) {
-                memmove(&swBuffer[1], &swBuffer[0], maxIndex * sizeof(SuggestedWord*));
+                memmove(&swBuffer[1], &swBuffer[0], maxIndex * sizeof(SuggestedWord *));
                 swBuffer[0] = nsMaxSw;
             }
         }
@@ -138,11 +140,13 @@
                 continue;
             }
             const unsigned int wordLength = sw->mWordLength;
-            char *targetAdr = (char*) outputChars + i * MAX_WORD_LENGTH * sizeof(short);
+            char *targetAddress = reinterpret_cast<char *>(outputChars)
+                    + i * MAX_WORD_LENGTH * sizeof(short);
             frequencies[i] = sw->mScore;
-            memcpy(targetAdr, sw->mWord, (wordLength) * sizeof(short));
+            outputTypes[i] = sw->mType;
+            memcpy(targetAddress, sw->mWord, (wordLength) * sizeof(short));
             if (wordLength < MAX_WORD_LENGTH) {
-                ((unsigned short*) targetAdr)[wordLength] = 0;
+                reinterpret_cast<unsigned short *>(targetAddress)[wordLength] = 0;
             }
             sw->mUsed = false;
         }
@@ -191,10 +195,10 @@
     };
 
     SuggestedWord *getFreeSuggestedWord(int score, unsigned short *word,
-            int wordLength) {
+            int wordLength, int type) {
         for (unsigned int i = 0; i < MAX_WORD_LENGTH; ++i) {
             if (!mSuggestedWords[i].mUsed) {
-                mSuggestedWords[i].setParams(score, word, wordLength);
+                mSuggestedWords[i].setParams(score, word, wordLength, type);
                 return &mSuggestedWords[i];
             }
         }
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 9ebd3bb..25e1740 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -112,7 +112,7 @@
 
         public static String getHelp() {
             return "Usage: makedict "
-                    + "[-s <unigrams.xml> [-b <bigrams.xml>] [-c <shortcuts.xml>] "
+                    + "[-s <unigrams.xml> [-b <bigrams.xml>] [-c <shortcuts_and_whitelist.xml>] "
                     + "| -s <binary input>] [-d <binary output format version 2>] "
                     + "[-d1 <binary output format version 1>] [-x <xml output>] [-2]\n"
                     + "\n"
@@ -246,7 +246,7 @@
      * Read a dictionary from a unigram XML file, and optionally a bigram XML file.
      *
      * @param unigramXmlFilename the name of the unigram XML file. May not be null.
-     * @param shortcutXmlFilename the name of the shortcut XML file, or null if there is none.
+     * @param shortcutXmlFilename the name of the shortcut/whitelist XML file, or null if none.
      * @param bigramXmlFilename the name of the bigram XML file. Pass null if there are no bigrams.
      * @return the read dictionary.
      * @throws FileNotFoundException if one of the files can't be found
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
index 8e2e735..9ce8c49 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
@@ -90,6 +90,10 @@
 
         public FusionDictionary getFinalDictionary() {
             final FusionDictionary dict = mDictionary;
+            for (final String shortcutOnly : mShortcutsMap.keySet()) {
+                if (dict.hasWord(shortcutOnly)) continue;
+                dict.add(shortcutOnly, 0, mShortcutsMap.get(shortcutOnly));
+            }
             mDictionary = null;
             mShortcutsMap.clear();
             mWord = "";
@@ -179,7 +183,7 @@
                 mSrc = attrs.getValue(uri, SRC_ATTRIBUTE);
             } else if (DST_TAG.equals(localName)) {
                 String dst = attrs.getValue(uri, DST_ATTRIBUTE);
-                int freq = Integer.parseInt(attrs.getValue(uri, DST_FREQ));
+                int freq = getValueFromFreqString(attrs.getValue(uri, DST_FREQ));
                 WeightedString bigram = new WeightedString(dst, freq / XML_TO_MEMORY_RATIO);
                 ArrayList<WeightedString> bigramList = mAssocMap.get(mSrc);
                 if (null == bigramList) bigramList = new ArrayList<WeightedString>();
@@ -188,6 +192,10 @@
             }
         }
 
+        protected int getValueFromFreqString(final String freqString) {
+            return Integer.parseInt(freqString);
+        }
+
         // This may return an empty map, but will never return null.
         public HashMap<String, ArrayList<WeightedString>> getAssocMap() {
             return mAssocMap;
@@ -216,22 +224,40 @@
     }
 
     /**
-     * SAX handler for a shortcut XML file.
+     * SAX handler for a shortcut & whitelist XML file.
      */
-    static private class ShortcutHandler extends AssociativeListHandler {
+    static private class ShortcutAndWhitelistHandler extends AssociativeListHandler {
         private final static String ENTRY_TAG = "entry";
         private final static String ENTRY_ATTRIBUTE = "shortcut";
         private final static String TARGET_TAG = "target";
         private final static String REPLACEMENT_ATTRIBUTE = "replacement";
         private final static String TARGET_PRIORITY_ATTRIBUTE = "priority";
+        private final static String WHITELIST_MARKER = "whitelist";
+        private final static int WHITELIST_FREQ_VALUE = 15;
+        private final static int MIN_FREQ = 0;
+        private final static int MAX_FREQ = 14;
 
-        public ShortcutHandler() {
+        public ShortcutAndWhitelistHandler() {
             super(ENTRY_TAG, ENTRY_ATTRIBUTE, TARGET_TAG, REPLACEMENT_ATTRIBUTE,
                     TARGET_PRIORITY_ATTRIBUTE);
         }
 
+        @Override
+        protected int getValueFromFreqString(final String freqString) {
+            if (WHITELIST_MARKER.equals(freqString)) {
+                return WHITELIST_FREQ_VALUE;
+            } else {
+                final int intValue = super.getValueFromFreqString(freqString);
+                if (intValue < MIN_FREQ || intValue > MAX_FREQ) {
+                    throw new RuntimeException("Shortcut freq out of range. Accepted range is "
+                            + MIN_FREQ + ".." + MAX_FREQ);
+                }
+                return intValue;
+            }
+        }
+
         // As per getAssocMap(), this never returns null.
-        public HashMap<String, ArrayList<WeightedString>> getShortcutMap() {
+        public HashMap<String, ArrayList<WeightedString>> getShortcutAndWhitelistMap() {
             return getAssocMap();
         }
     }
@@ -243,7 +269,7 @@
      * representation.
      *
      * @param unigrams the file to read the data from.
-     * @param shortcuts the file to read the shortcuts from, or null.
+     * @param shortcuts the file to read the shortcuts & whitelist from, or null.
      * @param bigrams the file to read the bigrams from, or null.
      * @return the in-memory representation of the dictionary.
      */
@@ -256,11 +282,12 @@
         final BigramHandler bigramHandler = new BigramHandler();
         if (null != bigrams) parser.parse(bigrams, bigramHandler);
 
-        final ShortcutHandler shortcutHandler = new ShortcutHandler();
-        if (null != shortcuts) parser.parse(shortcuts, shortcutHandler);
+        final ShortcutAndWhitelistHandler shortcutAndWhitelistHandler =
+                new ShortcutAndWhitelistHandler();
+        if (null != shortcuts) parser.parse(shortcuts, shortcutAndWhitelistHandler);
 
         final UnigramHandler unigramHandler =
-                new UnigramHandler(shortcutHandler.getShortcutMap());
+                new UnigramHandler(shortcutAndWhitelistHandler.getShortcutAndWhitelistMap());
         parser.parse(unigrams, unigramHandler);
         final FusionDictionary dict = unigramHandler.getFinalDictionary();
         final HashMap<String, ArrayList<WeightedString>> bigramMap = bigramHandler.getBigramMap();
@@ -280,7 +307,7 @@
      *
      * This method reads data from the parser and creates a new FusionDictionary with it.
      * The format parsed by this method is the format used before Ice Cream Sandwich,
-     * which has no support for bigrams or shortcuts.
+     * which has no support for bigrams or shortcuts/whitelist.
      * It is important to note that this method expects the parser to have already eaten
      * the first, all-encompassing tag.
      *
@@ -291,7 +318,7 @@
     /**
      * Writes a dictionary to an XML file.
      *
-     * The output format is the "second" format, which supports bigrams and shortcuts.
+     * The output format is the "second" format, which supports bigrams and shortcuts/whitelist.
      *
      * @param destination a destination stream to write to.
      * @param dict the dictionary to write.