Merge "Change accessibility event type for app-driven speech." into 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/res/values-in/strings.xml b/java/res/values-in/strings.xml
index 8273d54..9229910 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -140,7 +140,7 @@
     <string name="enable" msgid="5031294444630523247">"Aktifkan"</string>
     <string name="not_now" msgid="6172462888202790482">"Nanti saja"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Sudah ada gaya masukan yang sama: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
-    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modus studi daya guna"</string>
+    <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mode studi daya guna"</string>
     <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Setelan durasi getaran saat tombol ditekan"</string>
     <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Setelan volume suara saat tombol ditekan"</string>
 </resources>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 97d88af..c0e6aa8 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -59,6 +59,9 @@
     }
 
     public Keyboard getKeyboard() {
+        if (mKeyboard == null) {
+            throw new IllegalStateException("keyboard isn't set");
+        }
         return mKeyboard;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 3abe890..9198500 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -219,6 +219,11 @@
         return code >= CODE_SPACE;
     }
 
+    @Override
+    public String toString() {
+        return mId.toString();
+    }
+
     public static class Params {
         public KeyboardId mId;
         public int mThemeId;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 0e6de70..fcf97b9 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -108,14 +108,9 @@
     private int mDelayAfterPreview;
     private final PreviewPlacerView mPreviewPlacerView;
 
-    /** True if {@link KeyboardView} should handle gesture events. */
-    protected boolean mShouldHandleGesture;
-
     // Drawing
     /** True if the entire keyboard needs to be dimmed. */
     private boolean mNeedsToDimEntireKeyboard;
-    /** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/
-    private boolean mBufferNeedsUpdate;
     /** True if all keys should be drawn */
     private boolean mInvalidateAllKeys;
     /** The keys that should be drawn */
@@ -438,9 +433,8 @@
         return mShowKeyPreviewPopup;
     }
 
-    public void setGestureHandlingMode(boolean shouldHandleGesture,
-            boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
-        mShouldHandleGesture = shouldHandleGesture;
+    public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
+            boolean drawsGestureFloatingPreviewText) {
         mPreviewPlacerView.setGesturePreviewMode(
                 drawsGesturePreviewTrail, drawsGestureFloatingPreviewText);
     }
@@ -463,8 +457,9 @@
             onDrawKeyboard(canvas);
             return;
         }
-        if (mBufferNeedsUpdate || mOffscreenBuffer == null) {
-            mBufferNeedsUpdate = false;
+
+        final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
+        if (bufferNeedsUpdates || mOffscreenBuffer == null) {
             if (maybeAllocateOffscreenBuffer()) {
                 mInvalidateAllKeys = true;
                 // TODO: Stop using the offscreen canvas even when in software rendering
@@ -528,13 +523,12 @@
         }
         if (!isHardwareAccelerated) {
             canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
-        }
-
-        // Draw keyboard background.
-        canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
-        final Drawable background = getBackground();
-        if (background != null) {
-            background.draw(canvas);
+            // Draw keyboard background.
+            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+            final Drawable background = getBackground();
+            if (background != null) {
+                background.draw(canvas);
+            }
         }
 
         // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
@@ -1052,7 +1046,6 @@
     public void invalidateAllKeys() {
         mInvalidatedKeys.clear();
         mInvalidateAllKeys = true;
-        mBufferNeedsUpdate = true;
         invalidate();
     }
 
@@ -1070,9 +1063,7 @@
         mInvalidatedKeys.add(key);
         final int x = key.mX + getPaddingLeft();
         final int y = key.mY + getPaddingTop();
-        mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
-        mBufferNeedsUpdate = true;
-        invalidate(mWorkingRect);
+        invalidate(x, y, x + key.mWidth, y + key.mHeight);
     }
 
     public void closing() {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index fe9cb94..9590290 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -482,7 +482,7 @@
         super.setKeyboard(keyboard);
         mKeyDetector.setKeyboard(
                 keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
-        PointerTracker.setKeyDetector(mKeyDetector, mShouldHandleGesture);
+        PointerTracker.setKeyDetector(mKeyDetector);
         mTouchScreenRegulator.setKeyboard(keyboard);
         mMoreKeysPanelCache.clear();
 
@@ -500,12 +500,13 @@
         AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
     }
 
-    @Override
-    public void setGestureHandlingMode(final boolean shouldHandleGesture,
-            boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) {
-        super.setGestureHandlingMode(shouldHandleGesture, drawsGesturePreviewTrail,
-                drawsGestureFloatingPreviewText);
-        PointerTracker.setKeyDetector(mKeyDetector, shouldHandleGesture);
+    // Note that this method is called from a non-UI thread.
+    public void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
+        PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
+    }
+
+    public void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
+        PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser);
     }
 
     /**
@@ -834,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/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index a183546..cd4e300 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -39,11 +39,7 @@
 
         Key nearestKey = null;
         int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
-        final Keyboard keyboard = getKeyboard();
-        if (keyboard == null) {
-            throw new NullPointerException("Keyboard isn't set");
-        }
-        for (final Key key : keyboard.mKeys) {
+        for (final Key key : getKeyboard().mKeys) {
             final int dist = key.squaredDistanceToEdge(touchX, touchY);
             if (dist < nearestDist) {
                 nearestKey = key;
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 184011f..7d565a6 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -34,7 +34,7 @@
 
 import java.util.ArrayList;
 
-public class PointerTracker implements PointerTrackerQueue.ElementActions {
+public class PointerTracker implements PointerTrackerQueue.Element {
     private static final String TAG = PointerTracker.class.getSimpleName();
     private static final boolean DEBUG_EVENT = false;
     private static final boolean DEBUG_MOVE_EVENT = false;
@@ -43,6 +43,9 @@
 
     /** True if {@link PointerTracker}s should handle gesture events. */
     private static boolean sShouldHandleGesture = false;
+    private static boolean sMainDictionaryAvailable = false;
+    private static boolean sGestureHandlingEnabledByInputField = false;
+    private static boolean sGestureHandlingEnabledByUser = false;
 
     private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec
 
@@ -126,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;
 
@@ -198,7 +197,6 @@
         sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
 
         setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT);
-        updateGestureHandlingMode(null, false /* shouldHandleGesture */);
     }
 
     public static void setParameters(MainKeyboardView.PointerTrackerParams params) {
@@ -207,14 +205,22 @@
                 params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance);
     }
 
-    private static void updateGestureHandlingMode(Keyboard keyboard, boolean shouldHandleGesture) {
-        if (!shouldHandleGesture
-                || AccessibilityUtils.getInstance().isTouchExplorationEnabled()
-                || (keyboard != null && keyboard.mId.passwordInput())) {
-            sShouldHandleGesture = false;
-        } else {
-            sShouldHandleGesture = true;
-        }
+    private static void updateGestureHandlingMode() {
+        sShouldHandleGesture = sMainDictionaryAvailable
+                && sGestureHandlingEnabledByInputField
+                && sGestureHandlingEnabledByUser
+                && !AccessibilityUtils.getInstance().isTouchExplorationEnabled();
+    }
+
+    // Note that this method is called from a non-UI thread.
+    public static void setMainDictionaryAvailability(boolean mainDictionaryAvailable) {
+        sMainDictionaryAvailable = mainDictionaryAvailable;
+        updateGestureHandlingMode();
+    }
+
+    public static void setGestureHandlingEnabledByUser(boolean gestureHandlingEnabledByUser) {
+        sGestureHandlingEnabledByUser = gestureHandlingEnabledByUser;
+        updateGestureHandlingMode();
     }
 
     public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) {
@@ -241,7 +247,7 @@
         }
     }
 
-    public static void setKeyDetector(KeyDetector keyDetector, boolean shouldHandleGesture) {
+    public static void setKeyDetector(KeyDetector keyDetector) {
         final int trackersSize = sTrackers.size();
         for (int i = 0; i < trackersSize; ++i) {
             final PointerTracker tracker = sTrackers.get(i);
@@ -250,7 +256,8 @@
             tracker.mKeyboardLayoutHasBeenChanged = true;
         }
         final Keyboard keyboard = keyDetector.getKeyboard();
-        updateGestureHandlingMode(keyboard, shouldHandleGesture);
+        sGestureHandlingEnabledByInputField = !keyboard.mId.passwordInput();
+        updateGestureHandlingMode();
     }
 
     public static void dismissAllKeyPreviews() {
@@ -401,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) {
@@ -514,7 +520,7 @@
 
     public void drawGestureTrail(Canvas canvas, Paint paint) {
         if (mInGesture) {
-            mGestureStroke.drawGestureTrail(canvas, paint, mLastX, mLastY);
+            mGestureStroke.drawGestureTrail(canvas, paint);
         }
     }
 
@@ -574,7 +580,6 @@
         mListener.onEndBatchInput(batchPoints);
         clearBatchInputRecognitionStateOfThisPointerTracker();
         clearBatchInputPointsOfAllPointerTrackers();
-        sWasInGesture = true;
     }
 
     private void abortBatchInput() {
@@ -707,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();
             }
         }
@@ -990,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/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index bd16480..1c7ceaf 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,85 +18,146 @@
 
 import android.util.Log;
 
-import java.util.Iterator;
-import java.util.LinkedList;
+import java.util.ArrayList;
 
 public class PointerTrackerQueue {
     private static final String TAG = PointerTrackerQueue.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    public interface ElementActions {
+    public interface Element {
         public boolean isModifier();
         public boolean isInSlidingKeyInput();
         public void onPhantomUpEvent(long eventTime);
     }
 
-    // TODO: Use ring buffer instead of {@link LinkedList}.
-    private final LinkedList<ElementActions> mQueue = new LinkedList<ElementActions>();
+    private static final int INITIAL_CAPACITY = 10;
+    private final ArrayList<Element> mExpandableArrayOfActivePointers =
+            new ArrayList<Element>(INITIAL_CAPACITY);
+    private int mArraySize = 0;
 
-    public int size() {
-        return mQueue.size();
+    public synchronized int size() {
+        return mArraySize;
     }
 
-    public synchronized void add(ElementActions tracker) {
-        mQueue.add(tracker);
+    public synchronized void add(final Element pointer) {
+        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+        final int arraySize = mArraySize;
+        if (arraySize < expandableArray.size()) {
+            expandableArray.set(arraySize, pointer);
+        } else {
+            expandableArray.add(pointer);
+        }
+        mArraySize = arraySize + 1;
     }
 
-    public synchronized void remove(ElementActions tracker) {
-        mQueue.remove(tracker);
+    public synchronized void remove(final Element pointer) {
+        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+        final int arraySize = mArraySize;
+        int newSize = 0;
+        for (int index = 0; index < arraySize; index++) {
+            final Element element = expandableArray.get(index);
+            if (element == pointer) {
+                if (newSize != index) {
+                    Log.w(TAG, "Found duplicated element in remove: " + pointer);
+                }
+                continue; // Remove this element from the expandableArray.
+            }
+            if (newSize != index) {
+                // Shift this element toward the beginning of the expandableArray.
+                expandableArray.set(newSize, element);
+            }
+            newSize++;
+        }
+        mArraySize = newSize;
     }
 
-    public synchronized void releaseAllPointersOlderThan(ElementActions tracker,
-            long eventTime) {
+    public synchronized void releaseAllPointersOlderThan(final Element pointer,
+            final long eventTime) {
         if (DEBUG) {
-            Log.d(TAG, "releaseAllPoniterOlderThan: " + tracker + " " + this);
+            Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
         }
-        if (!mQueue.contains(tracker)) {
-            return;
-        }
-        final Iterator<ElementActions> it = mQueue.iterator();
-        while (it.hasNext()) {
-            final ElementActions t = it.next();
-            if (t == tracker) {
-                break;
+        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+        final int arraySize = mArraySize;
+        int newSize, index;
+        for (newSize = index = 0; index < arraySize; index++) {
+            final Element element = expandableArray.get(index);
+            if (element == pointer) {
+                break; // Stop releasing elements.
             }
-            if (!t.isModifier()) {
-                t.onPhantomUpEvent(eventTime);
-                it.remove();
+            if (!element.isModifier()) {
+                element.onPhantomUpEvent(eventTime);
+                continue; // Remove this element from the expandableArray.
+            }
+            if (newSize != index) {
+                // Shift this element toward the beginning of the expandableArray.
+                expandableArray.set(newSize, element);
+            }
+            newSize++;
+        }
+        // Shift rest of the expandableArray.
+        int count = 0;
+        for (; index < arraySize; index++) {
+            final Element element = expandableArray.get(index);
+            if (element == pointer) {
+                if (count > 0) {
+                    Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
+                            + pointer);
+                }
+                count++;
+            }
+            if (newSize != index) {
+                expandableArray.set(newSize, expandableArray.get(index));
+                newSize++;
             }
         }
+        mArraySize = newSize;
     }
 
-    public void releaseAllPointers(long eventTime) {
+    public void releaseAllPointers(final long eventTime) {
         releaseAllPointersExcept(null, eventTime);
     }
 
-    public synchronized void releaseAllPointersExcept(ElementActions tracker, long eventTime) {
+    public synchronized void releaseAllPointersExcept(final Element pointer,
+            final long eventTime) {
         if (DEBUG) {
-            if (tracker == null) {
+            if (pointer == null) {
                 Log.d(TAG, "releaseAllPoniters: " + this);
             } else {
-                Log.d(TAG, "releaseAllPoniterExcept: " + tracker + " " + this);
+                Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
             }
         }
-        final Iterator<ElementActions> it = mQueue.iterator();
-        while (it.hasNext()) {
-            final ElementActions t = it.next();
-            if (t != tracker) {
-                t.onPhantomUpEvent(eventTime);
-                it.remove();
+        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+        final int arraySize = mArraySize;
+        int newSize = 0, count = 0;
+        for (int index = 0; index < arraySize; index++) {
+            final Element element = expandableArray.get(index);
+            if (element == pointer) {
+                if (count > 0) {
+                    Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " + pointer);
+                }
+                count++;
+            } else {
+                element.onPhantomUpEvent(eventTime);
+                continue; // Remove this element from the expandableArray.
             }
+            if (newSize != index) {
+                // Shift this element toward the beginning of the expandableArray.
+                expandableArray.set(newSize, element);
+            }
+            newSize++;
         }
+        mArraySize = newSize;
     }
 
-    public synchronized boolean hasModifierKeyOlderThan(ElementActions tracker) {
-        final Iterator<ElementActions> it = mQueue.iterator();
-        while (it.hasNext()) {
-            final ElementActions t = it.next();
-            if (t == tracker) {
-                break;
+    public synchronized boolean hasModifierKeyOlderThan(final Element pointer) {
+        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+        final int arraySize = mArraySize;
+        for (int index = 0; index < arraySize; index++) {
+            final Element element = expandableArray.get(index);
+            if (element == pointer) {
+                return false; // Stop searching modifier key.
             }
-            if (t.isModifier()) {
+            if (element.isModifier()) {
                 return true;
             }
         }
@@ -104,8 +165,11 @@
     }
 
     public synchronized boolean isAnyInSlidingKeyInput() {
-        for (final ElementActions tracker : mQueue) {
-            if (tracker.isInSlidingKeyInput()) {
+        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+        final int arraySize = mArraySize;
+        for (int index = 0; index < arraySize; index++) {
+            final Element element = expandableArray.get(index);
+            if (element.isInSlidingKeyInput()) {
                 return true;
             }
         }
@@ -113,12 +177,15 @@
     }
 
     @Override
-    public String toString() {
+    public synchronized String toString() {
         final StringBuilder sb = new StringBuilder();
-        for (final ElementActions tracker : mQueue) {
+        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+        final int arraySize = mArraySize;
+        for (int index = 0; index < arraySize; index++) {
+            final Element element = expandableArray.get(index);
             if (sb.length() > 0)
                 sb.append(" ");
-            sb.append(tracker.toString());
+            sb.append(element.toString());
         }
         return "[" + sb.toString() + "]";
     }
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 534cffb..f0f5cd3 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -51,13 +51,15 @@
     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];
     private final int[] mOutputTypes = new int[MAX_RESULTS];
 
     private final boolean mUseFullEditDistance;
+    private final DicTraverseSession mDicTraverseSession;
 
     /**
      * Constructor for the binary dictionary. This is supposed to be called from the
@@ -76,6 +78,8 @@
         super(dictType);
         mUseFullEditDistance = useFullEditDistance;
         loadDictionary(filename, offset, length);
+        mDicTraverseSession = new DicTraverseSession(locale);
+        mDicTraverseSession.initSession(mNativeDict);
     }
 
     static {
@@ -86,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, int[] xCoordinates,
-            int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodes, int codesSize,
-            int commitPoint, boolean isGesture,
+    private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
+            int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
+            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);
@@ -107,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());
@@ -119,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);
             }
         }
 
@@ -127,9 +128,9 @@
         final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
         // proximityInfo and/or prevWordForBigrams may not be null.
         final int tmpCount = getSuggestionsNative(mNativeDict,
-                proximityInfo.getNativeProximityInfo(), 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);
 
@@ -142,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;
@@ -155,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
@@ -172,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
@@ -187,6 +187,7 @@
 
     @Override
     public synchronized void close() {
+        mDicTraverseSession.close();
         closeInternal();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
new file mode 100644
index 0000000..c768153
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import java.util.Locale;
+
+public class DicTraverseSession {
+    static {
+        JniUtils.loadNativeLibrary();
+    }
+    private native long setDicTraverseSessionNative(String locale);
+    private native void initDicTraverseSessionNative(long nativeDicTraverseSession,
+            long dictionary, int[] previousWord, int previousWordLength);
+    private native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
+
+    private long mNativeDicTraverseSession;
+
+    public DicTraverseSession(Locale locale) {
+        mNativeDicTraverseSession = createNativeDicTraverseSession(
+                locale != null ? locale.toString() : "");
+    }
+
+    public long getSession() {
+        return mNativeDicTraverseSession;
+    }
+
+    public void initSession(long dictionary) {
+        initSession(dictionary, null, 0);
+    }
+
+    public void initSession(long dictionary, int[] previousWord, int previousWordLength) {
+        initDicTraverseSessionNative(
+                mNativeDicTraverseSession, dictionary, previousWord, previousWordLength);
+    }
+
+    private final long createNativeDicTraverseSession(String locale) {
+        return setDicTraverseSessionNative(locale);
+    }
+
+    private void closeInternal() {
+        if (mNativeDicTraverseSession != 0) {
+            releaseDicTraverseSessionNative(mNativeDicTraverseSession);
+            mNativeDicTraverseSession = 0;
+        }
+    }
+
+    public void close() {
+        closeInternal();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            closeInternal();
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 25cddb2..4a7d040 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -433,10 +433,14 @@
         resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
     }
 
+    // Note that this method is called from a non-UI thread.
     @Override
     public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable) {
         mIsMainDictionaryAvailable = isMainDictionaryAvailable;
-        updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
+        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        if (mainKeyboardView != null) {
+            mainKeyboardView.setMainDictionaryAvailability(isMainDictionaryAvailable);
+        }
     }
 
     private void initSuggest() {
@@ -517,7 +521,7 @@
 
     /* package private */ void resetSuggestMainDict() {
         final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
-        mSuggest.resetMainDict(this, subtypeLocale);
+        mSuggest.resetMainDict(this, subtypeLocale, this /* SuggestInitializationListener */);
         mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
     }
 
@@ -701,7 +705,6 @@
             }
 
             switcher.loadKeyboard(editorInfo, mCurrentSettings);
-            updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
         }
         setSuggestionStripShownInternal(
                 isSuggestionsStripVisible(), /* needsInputViewShown */ false);
@@ -719,8 +722,12 @@
         mHandler.cancelUpdateSuggestionStrip();
         mHandler.cancelDoubleSpacesTimer();
 
+        mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
         mainKeyboardView.setKeyPreviewPopupEnabled(mCurrentSettings.mKeyPreviewPopupOn,
                 mCurrentSettings.mKeyPreviewPopupDismissDelay);
+        mainKeyboardView.setGestureHandlingEnabledByUser(mCurrentSettings.mGestureInputEnabled);
+        mainKeyboardView.setGesturePreviewMode(mCurrentSettings.mGesturePreviewTrailEnabled,
+                mCurrentSettings.mGestureFloatingPreviewTextEnabled);
 
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
     }
@@ -2103,7 +2110,6 @@
         if (mKeyboardSwitcher.getMainKeyboardView() != null) {
             // Reload keyboard because the current language has been changed.
             mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mCurrentSettings);
-            updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability();
         }
         // Since we just changed languages, we should re-evaluate suggestions with whatever word
         // we are currently composing. If we are not composing anything, we may want to display
@@ -2111,17 +2117,6 @@
         mHandler.postUpdateSuggestionStrip();
     }
 
-    private void updateKeyboardViewGestureHandlingModeByMainDictionaryAvailability() {
-        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
-        if (mainKeyboardView != null) {
-            final boolean shouldHandleGesture = mCurrentSettings.mGestureInputEnabled
-                    && mIsMainDictionaryAvailable;
-            mainKeyboardView.setGestureHandlingMode(shouldHandleGesture,
-                    mCurrentSettings.mGesturePreviewTrailEnabled,
-                    mCurrentSettings.mGestureFloatingPreviewTextEnabled);
-        }
-    }
-
     // TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to
     // {@link KeyboardSwitcher}. Called from KeyboardSwitcher
     public void hapticAndAudioFeedback(final int primaryCode) {
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 5e2a041..8a2341d 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -60,13 +60,11 @@
 
     // Locale used for upper- and title-casing words
     private final Locale mLocale;
-    private final SuggestInitializationListener mListener;
 
     public Suggest(final Context context, final Locale locale,
             final SuggestInitializationListener listener) {
-        initAsynchronously(context, locale);
+        initAsynchronously(context, locale, listener);
         mLocale = locale;
-        mListener = listener;
     }
 
     /* package for test */ Suggest(final Context context, final File dictionary,
@@ -74,7 +72,6 @@
         final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
                 startOffset, length /* useFullEditDistance */, false, locale);
         mLocale = locale;
-        mListener = null;
         mMainDictionary = mainDict;
         addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
         initWhitelistAndAutocorrectAndPool(context, locale);
@@ -85,8 +82,9 @@
         addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_WHITELIST, mWhiteListDictionary);
     }
 
-    private void initAsynchronously(final Context context, final Locale locale) {
-        resetMainDict(context, locale);
+    private void initAsynchronously(final Context context, final Locale locale,
+            final SuggestInitializationListener listener) {
+        resetMainDict(context, locale, listener);
 
         // TODO: read the whitelist and init the pool asynchronously too.
         // initPool should be done asynchronously now that the pool is thread-safe.
@@ -104,10 +102,11 @@
         }
     }
 
-    public void resetMainDict(final Context context, final Locale locale) {
+    public void resetMainDict(final Context context, final Locale locale,
+            final SuggestInitializationListener listener) {
         mMainDictionary = null;
-        if (mListener != null) {
-            mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
+        if (listener != null) {
+            listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
         }
         new Thread("InitializeBinaryDictionary") {
             @Override
@@ -116,8 +115,8 @@
                         DictionaryFactory.createMainDictionaryFromManager(context, locale);
                 addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
                 mMainDictionary = newMainDict;
-                if (mListener != null) {
-                    mListener.onUpdateMainDictionaryAvailability(hasMainDictionary());
+                if (listener != null) {
+                    listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
                 }
             }
         }.start();
@@ -209,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/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 0171dc0..06f5db7 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -193,7 +193,7 @@
             if (shouldFilterOut(inText, mScript)) {
                 DictAndProximity dictInfo = null;
                 try {
-                    dictInfo = mDictionaryPool.takeOrGetNull();
+                    dictInfo = mDictionaryPool.pollWithDefaultTimeout();
                     if (null == dictInfo) {
                         return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
                     }
@@ -236,7 +236,7 @@
             boolean isInDict = true;
             DictAndProximity dictInfo = null;
             try {
-                dictInfo = mDictionaryPool.takeOrGetNull();
+                dictInfo = mDictionaryPool.pollWithDefaultTimeout();
                 if (null == dictInfo) {
                     return AndroidSpellCheckerService.getNotInDictEmptySuggestions();
                 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index 8fc632e..83f82fa 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -16,14 +16,24 @@
 
 package com.android.inputmethod.latin.spellcheck;
 
+import android.util.Log;
+
 import java.util.Locale;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A blocking queue that creates dictionaries up to a certain limit as necessary.
+ * As a deadlock-detecting device, if waiting for more than TIMEOUT = 3 seconds, we
+ * will clear the queue and generate its contents again. This is transparent for
+ * the client code, but may help with sloppy clients.
  */
 @SuppressWarnings("serial")
 public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
+    private final static String TAG = DictionaryPool.class.getSimpleName();
+    // How many seconds we wait for a dictionary to become available. Past this delay, we give up in
+    // fear some bug caused a deadlock, and reset the whole pool.
+    private final static int TIMEOUT = 3;
     private final AndroidSpellCheckerService mService;
     private final int mMaxSize;
     private final Locale mLocale;
@@ -41,13 +51,23 @@
     }
 
     @Override
-    public DictAndProximity take() throws InterruptedException {
+    public DictAndProximity poll(final long timeout, final TimeUnit unit)
+            throws InterruptedException {
         final DictAndProximity dict = poll();
         if (null != dict) return dict;
         synchronized(this) {
             if (mSize >= mMaxSize) {
-                // Our pool is already full. Wait until some dictionary is ready.
-                return super.take();
+                // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT
+                // expires to avoid a deadlock.
+                final DictAndProximity result = super.poll(timeout, unit);
+                if (null == result) {
+                    Log.e(TAG, "Deadlock detected ! Resetting dictionary pool");
+                    clear();
+                    mSize = 1;
+                    return mService.createDictAndProximity(mLocale);
+                } else {
+                    return result;
+                }
             } else {
                 ++mSize;
                 return mService.createDictAndProximity(mLocale);
@@ -56,9 +76,9 @@
     }
 
     // Convenience method
-    public DictAndProximity takeOrGetNull() {
+    public DictAndProximity pollWithDefaultTimeout() {
         try {
-            return take();
+            return poll(TIMEOUT, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
             return null;
         }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 0103e84..bd92d88 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -111,6 +111,7 @@
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+            NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
         };
         static {
             buildProximityIndices(PROXIMITY, INDICES);
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 9f99583..567648f 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -36,7 +36,7 @@
 LATIN_IME_JNI_SRC_FILES := \
     com_android_inputmethod_keyboard_ProximityInfo.cpp \
     com_android_inputmethod_latin_BinaryDictionary.cpp \
-    com_android_inputmethod_latin_NativeUtils.cpp \
+    com_android_inputmethod_latin_DicTraverseSession.cpp \
     jni_common.cpp
 
 LATIN_IME_CORE_SRC_FILES := \
@@ -46,6 +46,7 @@
     char_utils.cpp \
     correction.cpp \
     dictionary.cpp \
+    dic_traverse_wrapper.cpp \
     proximity_info.cpp \
     proximity_info_state.cpp \
     unigram_dictionary.cpp \
@@ -69,7 +70,6 @@
 LOCAL_MODULE := libjni_latinime_common_static
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_NDK_VERSION := 8
 LOCAL_SDK_VERSION := 14
 LOCAL_NDK_STL_VARIANT := stlport_static
 
@@ -93,7 +93,6 @@
 LOCAL_MODULE := libjni_latinime
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_NDK_VERSION := 8
 LOCAL_SDK_VERSION := 14
 LOCAL_NDK_STL_VARIANT := stlport_static
 
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index 74390cc..560b3a5 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -16,8 +16,6 @@
 
 #define LOG_TAG "LatinIME: jni: ProximityInfo"
 
-#include <string>
-
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "jni.h"
 #include "jni_common.h"
@@ -26,53 +24,27 @@
 namespace latinime {
 
 static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
-        jstring localejStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
-        jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityCharsArray,
-        jint keyCount, jintArray keyXCoordinateArray, jintArray keyYCoordinateArray,
-        jintArray keyWidthArray, jintArray keyHeightArray, jintArray keyCharCodeArray,
-        jfloatArray sweetSpotCenterXArray, jfloatArray sweetSpotCenterYArray,
-        jfloatArray sweetSpotRadiusArray) {
-    const char *localeStrPtr = env->GetStringUTFChars(localejStr, 0);
-    const std::string localeStr(localeStrPtr);
-    jint *proximityChars = env->GetIntArrayElements(proximityCharsArray, 0);
-    jint *keyXCoordinates = safeGetIntArrayElements(env, keyXCoordinateArray);
-    jint *keyYCoordinates = safeGetIntArrayElements(env, keyYCoordinateArray);
-    jint *keyWidths = safeGetIntArrayElements(env, keyWidthArray);
-    jint *keyHeights = safeGetIntArrayElements(env, keyHeightArray);
-    jint *keyCharCodes = safeGetIntArrayElements(env, keyCharCodeArray);
-    jfloat *sweetSpotCenterXs = safeGetFloatArrayElements(env, sweetSpotCenterXArray);
-    jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
-    jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
-    ProximityInfo *proximityInfo = new ProximityInfo(
-            localeStr, maxProximityCharsSize, displayWidth, displayHeight, gridWidth, gridHeight,
-            mostCommonkeyWidth, (const int32_t*)proximityChars, keyCount,
-            (const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
-            (const int32_t*)keyWidths, (const int32_t*)keyHeights, (const int32_t*)keyCharCodes,
-            (const float*)sweetSpotCenterXs, (const float*)sweetSpotCenterYs,
-            (const float*)sweetSpotRadii);
-    safeReleaseFloatArrayElements(env, sweetSpotRadiusArray, sweetSpotRadii);
-    safeReleaseFloatArrayElements(env, sweetSpotCenterYArray, sweetSpotCenterYs);
-    safeReleaseFloatArrayElements(env, sweetSpotCenterXArray, sweetSpotCenterXs);
-    safeReleaseIntArrayElements(env, keyCharCodeArray, keyCharCodes);
-    safeReleaseIntArrayElements(env, keyHeightArray, keyHeights);
-    safeReleaseIntArrayElements(env, keyWidthArray, keyWidths);
-    safeReleaseIntArrayElements(env, keyYCoordinateArray, keyYCoordinates);
-    safeReleaseIntArrayElements(env, keyXCoordinateArray, keyXCoordinates);
-    env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
-    env->ReleaseStringUTFChars(localejStr, localeStrPtr);
-    return (jlong)proximityInfo;
+        jstring localeJStr, jint maxProximityCharsSize, jint displayWidth, jint displayHeight,
+        jint gridWidth, jint gridHeight, jint mostCommonkeyWidth, jintArray proximityChars,
+        jint keyCount, jintArray keyXCoordinates, jintArray keyYCoordinates,
+        jintArray keyWidths, jintArray keyHeights, jintArray keyCharCodes,
+        jfloatArray sweetSpotCenterXs, jfloatArray sweetSpotCenterYs, jfloatArray sweetSpotRadii) {
+    ProximityInfo *proximityInfo = new ProximityInfo(env, localeJStr, maxProximityCharsSize,
+            displayWidth, displayHeight, gridWidth, gridHeight, mostCommonkeyWidth, proximityChars,
+            keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
+            sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
+    return reinterpret_cast<jlong>(proximityInfo);
 }
 
 static void latinime_Keyboard_release(JNIEnv *env, jobject object, jlong proximityInfo) {
-    ProximityInfo *pi = (ProximityInfo*)proximityInfo;
-    if (!pi) return;
+    ProximityInfo *pi = reinterpret_cast<ProximityInfo *>(proximityInfo);
     delete pi;
 }
 
 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 776f5f7..2add7c9 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>
@@ -30,13 +27,21 @@
 #include <sys/mman.h>
 #else // USE_MMAP_FOR_DICTIONARY
 #include <cstdlib>
+#include <cstdio> // for fopen() etc.
 #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;
 
-void releaseDictBuf(void *dictBuf, const size_t length, int fd);
+static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd);
 
 static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object,
         jstring sourceDir, jlong dictOffset, jlong dictSize,
@@ -44,11 +49,14 @@
         jint maxPredictions) {
     PROF_OPEN;
     PROF_START(66);
-    const char *sourceDirChars = env->GetStringUTFChars(sourceDir, 0);
-    if (sourceDirChars == 0) {
+    const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir);
+    if (sourceDirUtf8Length <= 0) {
         AKLOGE("DICT: Can't get sourceDir string");
         return 0;
     }
+    char sourceDirChars[sourceDirUtf8Length + 1];
+    env->GetStringUTFRegion(sourceDir, 0, env->GetStringLength(sourceDir), sourceDirChars);
+    sourceDirChars[sourceDirUtf8Length] = '\0';
     int fd = 0;
     void *dictBuf = 0;
     int adjust = 0;
@@ -68,7 +76,7 @@
         AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
         return 0;
     }
-    dictBuf = (void *)((char *)dictBuf + adjust);
+    dictBuf = static_cast<char *>(dictBuf) + adjust;
 #else // USE_MMAP_FOR_DICTIONARY
     /* malloc version */
     FILE *file = 0;
@@ -98,17 +106,16 @@
         return 0;
     }
 #endif // USE_MMAP_FOR_DICTIONARY
-    env->ReleaseStringUTFChars(sourceDir, sourceDirChars);
-
     if (!dictBuf) {
         AKLOGE("DICT: dictBuf is null");
         return 0;
     }
     Dictionary *dictionary = 0;
-    if (BinaryFormat::UNKNOWN_FORMAT == BinaryFormat::detectFormat((uint8_t*)dictBuf)) {
+    if (BinaryFormat::UNKNOWN_FORMAT
+            == BinaryFormat::detectFormat(static_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(static_cast<const char *>(dictBuf) - adjust, adjDictSize, fd);
 #else // USE_MMAP_FOR_DICTIONARY
         releaseDictBuf(dictBuf, 0, 0);
 #endif // USE_MMAP_FOR_DICTIONARY
@@ -122,106 +129,131 @@
 }
 
 static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jlong dict,
-        jlong proximityInfo, 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) {
-    Dictionary *dictionary = (Dictionary*) dict;
+        jlong proximityInfo, jlong dicTraverseSession, jintArray xCoordinatesArray,
+        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 = (ProximityInfo*)proximityInfo;
-    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;
+    ProximityInfo *pInfo = reinterpret_cast<ProximityInfo *>(proximityInfo);
+    void *traverseSession = reinterpret_cast<void *>(dicTraverseSession);
+
+    // 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, xCoordinates, yCoordinates, times, pointerIds,
-                inputCodes, arraySize, prevWordChars, prevWordLength, commitPoint, isGesture,
-                useFullEditDistance, (unsigned short*) outputChars, frequencies, spaceIndices,
-                outputTypes);
+        count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
+                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(
+            static_cast<unsigned short *>(beforeChars), beforeLength,
+            static_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(
+            static_cast<unsigned short *>(beforeChars), beforeLength,
+            static_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();
+    const void *dictBuf = dictionary->getDict();
     if (!dictBuf) return;
 #ifdef USE_MMAP_FOR_DICTIONARY
-    releaseDictBuf((void *)((char *)dictBuf - dictionary->getDictBufAdjust()),
+    releaseDictBuf(static_cast<const char *>(dictBuf) - dictionary->getDictBufAdjust(),
             dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
 #else // USE_MMAP_FOR_DICTIONARY
     releaseDictBuf(dictBuf, 0, 0);
@@ -229,9 +261,9 @@
     delete dictionary;
 }
 
-void releaseDictBuf(void *dictBuf, const size_t length, int fd) {
+static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd) {
 #ifdef USE_MMAP_FOR_DICTIONARY
-    int ret = munmap(dictBuf, length);
+    int ret = munmap(const_cast<void *>(dictBuf), length);
     if (ret != 0) {
         AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
     }
@@ -240,20 +272,24 @@
         AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
     }
 #else // USE_MMAP_FOR_DICTIONARY
-    free(dictBuf);
+    free(const_cast<void *>(dictBuf));
 #endif // USE_MMAP_FOR_DICTIONARY
 }
 
 static JNINativeMethod sMethods[] = {
-    {"openNative", "(Ljava/lang/String;JJIIIII)J", (void*)latinime_BinaryDictionary_open},
-    {"closeNative", "(J)V", (void*)latinime_BinaryDictionary_close},
-    {"getSuggestionsNative", "(JJ[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}
+    {"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",
+            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
new file mode 100644
index 0000000..5d405f1
--- /dev/null
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#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 {
+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, jobject object, jlong traverseSession,
+        jlong dictionary, jintArray previousWord, jint previousWordLength) {
+    void *ts = reinterpret_cast<void *>(traverseSession);
+    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_releaseDicTraverseSession(JNIEnv *env, jobject object, jlong traverseSession) {
+    void *ts = reinterpret_cast<void *>(traverseSession);
+    DicTraverseWrapper::releaseDicTraverseSession(ts);
+}
+
+static JNINativeMethod sMethods[] = {
+    {"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) {
+    const char *const kClassPathName = "com/android/inputmethod/latin/DicTraverseSession";
+    return registerNativeMethods(env, kClassPathName, sMethods,
+            sizeof(sMethods) / sizeof(sMethods[0]));
+}
+} // namespace latinime
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.h b/native/jni/com_android_inputmethod_latin_DicTraverseSession.h
similarity index 73%
rename from native/jni/com_android_inputmethod_latin_NativeUtils.h
rename to native/jni/com_android_inputmethod_latin_DicTraverseSession.h
index d1ffb8f..37531e9 100644
--- a/native/jni/com_android_inputmethod_latin_NativeUtils.h
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.h
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
-#define _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
+#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
+#define _COM_ANDROID_INPUTMETHOD_LATIN_DICTRAVERSESESSION_H
 
+#include "defines.h"
 #include "jni.h"
 
 namespace latinime {
-
-int register_NativeUtils(JNIEnv *env);
-
+int register_DicTraverseSession(JNIEnv *env);
 } // namespace latinime
-#endif // _COM_ANDROID_INPUTMETHOD_LATIN_NATIVEUTILS_H
+#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 105a4dc..0da1669 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -16,15 +16,15 @@
 
 #define LOG_TAG "LatinIME: jni"
 
+#include <cassert>
+
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
-#include "com_android_inputmethod_latin_NativeUtils.h"
+#include "com_android_inputmethod_latin_DicTraverseSession.h"
 #include "defines.h"
 #include "jni.h"
 #include "jni_common.h"
 
-#include <cassert>
-
 using namespace latinime;
 
 /*
@@ -34,7 +34,7 @@
     JNIEnv *env = 0;
     jint result = -1;
 
-    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+    if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
         AKLOGE("ERROR: GetEnv failed");
         goto bail;
     }
@@ -45,13 +45,13 @@
         goto bail;
     }
 
-    if (!register_ProximityInfo(env)) {
-        AKLOGE("ERROR: ProximityInfo native registration failed");
+    if (!register_DicTraverseSession(env)) {
+        AKLOGE("ERROR: DicTraverseSession native registration failed");
         goto bail;
     }
 
-    if (!register_NativeUtils(env)) {
-        AKLOGE("ERROR: NativeUtils native registration failed");
+    if (!register_ProximityInfo(env)) {
+        AKLOGE("ERROR: ProximityInfo native registration failed");
         goto bail;
     }
 
diff --git a/native/jni/jni_common.h b/native/jni/jni_common.h
index 658ff18..993f97e 100644
--- a/native/jni/jni_common.h
+++ b/native/jni/jni_common.h
@@ -24,32 +24,5 @@
 int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
         int numMethods);
 
-inline jint *safeGetIntArrayElements(JNIEnv *env, jintArray jArray) {
-    if (jArray) {
-        return env->GetIntArrayElements(jArray, 0);
-    } else {
-        return 0;
-    }
-}
-
-inline jfloat *safeGetFloatArrayElements(JNIEnv *env, jfloatArray jArray) {
-    if (jArray) {
-        return env->GetFloatArrayElements(jArray, 0);
-    } else {
-        return 0;
-    }
-}
-
-inline void safeReleaseIntArrayElements(JNIEnv *env, jintArray jArray, jint *cArray) {
-    if (jArray) {
-        env->ReleaseIntArrayElements(jArray, cArray, 0);
-    }
-}
-
-inline void safeReleaseFloatArrayElements(JNIEnv *env, jfloatArray jArray, jfloat *cArray) {
-    if (jArray) {
-        env->ReleaseFloatArrayElements(jArray, cArray, 0);
-    }
-}
 } // namespace latinime
 #endif // LATINIME_JNI_COMMON_H
diff --git a/native/jni/src/additional_proximity_chars.cpp b/native/jni/src/additional_proximity_chars.cpp
index de87646..f594927 100644
--- a/native/jni/src/additional_proximity_chars.cpp
+++ b/native/jni/src/additional_proximity_chars.cpp
@@ -17,7 +17,9 @@
 #include "additional_proximity_chars.h"
 
 namespace latinime {
-const std::string AdditionalProximityChars::LOCALE_EN_US("en");
+// TODO: Stop using hardcoded additional proximity characters.
+// TODO: Have proximity character informations in each language's binary dictionary.
+const char *AdditionalProximityChars::LOCALE_EN_US = "en";
 
 const int32_t AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
     'e', 'i', 'o', 'u'
diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/additional_proximity_chars.h
index ba76cfc..1fe996d 100644
--- a/native/jni/src/additional_proximity_chars.h
+++ b/native/jni/src/additional_proximity_chars.h
@@ -17,8 +17,8 @@
 #ifndef LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
 #define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
 
+#include <cstring>
 #include <stdint.h>
-#include <string>
 
 #include "defines.h"
 
@@ -27,7 +27,7 @@
 class AdditionalProximityChars {
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars);
-    static const std::string LOCALE_EN_US;
+    static const char *LOCALE_EN_US;
     static const int EN_US_ADDITIONAL_A_SIZE = 4;
     static const int32_t EN_US_ADDITIONAL_A[];
     static const int EN_US_ADDITIONAL_E_SIZE = 4;
@@ -39,14 +39,15 @@
     static const int EN_US_ADDITIONAL_U_SIZE = 4;
     static const int32_t EN_US_ADDITIONAL_U[];
 
-    static bool isEnLocale(const std::string *locale_str) {
-        return locale_str && locale_str->size() >= LOCALE_EN_US.size()
-                && LOCALE_EN_US.compare(0, LOCALE_EN_US.size(), *locale_str);
+    static bool isEnLocale(const char *localeStr) {
+        const size_t LOCALE_EN_US_SIZE = strlen(LOCALE_EN_US);
+        return localeStr && strlen(localeStr) >= LOCALE_EN_US_SIZE
+                && strncmp(localeStr, LOCALE_EN_US, LOCALE_EN_US_SIZE) == 0;
     }
 
  public:
-    static int getAdditionalCharsSize(const std::string *locale_str, const int32_t c) {
-        if (!isEnLocale(locale_str)) {
+    static int getAdditionalCharsSize(const char *localeStr, const int32_t c) {
+        if (!isEnLocale(localeStr)) {
             return 0;
         }
         switch(c) {
@@ -65,8 +66,8 @@
         }
     }
 
-    static const int32_t *getAdditionalChars(const std::string *locale_str, const int32_t c) {
-        if (!isEnLocale(locale_str)) {
+    static const int32_t *getAdditionalChars(const char *localeStr, const int32_t c) {
+        if (!isEnLocale(localeStr)) {
             return 0;
         }
         switch(c) {
@@ -84,10 +85,6 @@
             return 0;
         }
     }
-
-    static bool hasAdditionalChars(const std::string *locale_str, const int32_t c) {
-        return getAdditionalCharsSize(locale_str, c) > 0;
-    }
 };
 } // namespace latinime
 #endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
diff --git a/native/jni/src/bigram_dictionary.cpp b/native/jni/src/bigram_dictionary.cpp
index 2201711..f1d5380 100644
--- a/native/jni/src/bigram_dictionary.cpp
+++ b/native/jni/src/bigram_dictionary.cpp
@@ -60,15 +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(bigramFreq + (insertAt + 1),
+                bigramFreq + insertAt,
+                (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);
-        unsigned short *dest = bigramChars + (insertAt    ) * MAX_WORD_LENGTH;
+        memmove(bigramChars + (insertAt + 1) * MAX_WORD_LENGTH,
+                bigramChars + insertAt * MAX_WORD_LENGTH,
+                (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramChars[0]) * 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..223291f 100644
--- a/native/jni/src/char_utils.cpp
+++ b/native/jni/src/char_utils.cpp
@@ -885,16 +885,16 @@
 };
 
 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>(*static_cast<const unsigned short *>(a))
+            - static_cast<int>((static_cast<const struct LatinCapitalSmallPair *>(b))->capital);
 }
 
 unsigned short latin_tolower(unsigned short c) {
     struct LatinCapitalSmallPair *p =
-            (struct LatinCapitalSmallPair *)bsearch(&c, SORTED_CHAR_MAP,
+            static_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..4e21640 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 = static_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/src/defines.h b/native/jni/src/defines.h
index 31dd61e..484fc6b 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -265,6 +265,9 @@
 // This must be equal to ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE in KeyDetector.java
 #define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
 
+// Assuming locale strings such as en_US, sr-Latn etc.
+#define MAX_LOCALE_STRING_LENGTH 10
+
 // Word limit for sub queues used in WordsPriorityQueuePool.  Sub queues are temporary queues used
 // for better performance.
 // Holds up to 1 candidate for each word
diff --git a/native/jni/com_android_inputmethod_latin_NativeUtils.h b/native/jni/src/dic_traverse_wrapper.cpp
similarity index 64%
copy from native/jni/com_android_inputmethod_latin_NativeUtils.h
copy 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 ee55cfa..158c3fb 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(static_cast<unsigned char *>(dict)),
+      mOffsetDict((static_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() {
@@ -56,16 +55,18 @@
     delete mGestureDecoder;
 }
 
-int Dictionary::getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
-        int *times, int *pointerIds, int *codes, int codesSize, int *prevWordChars,
+int Dictionary::getSuggestions(ProximityInfo *proximityInfo, void *traverseSession,
+        int *xcoordinates, int *ycoordinates, int *times, int *pointerIds,
+        int *codes, int codesSize, int *prevWordChars,
         int prevWordLength, int commitPoint, bool isGesture,
         bool useFullEditDistance, unsigned short *outWords,
         int *frequencies, int *spaceIndices, int *outputTypes) {
     int result = 0;
     if (isGesture) {
-        mGestureDecoder->setPrevWord(prevWordChars, prevWordLength);
-        result = mGestureDecoder->getSuggestions(proximityInfo, xcoordinates, ycoordinates,
-                times, pointerIds, codes, codesSize, commitPoint,
+        DicTraverseWrapper::initDicTraverseSession(
+                traverseSession, this, prevWordChars, prevWordLength);
+        result = mGestureDecoder->getSuggestions(proximityInfo, traverseSession,
+                xcoordinates, ycoordinates, times, pointerIds, codes, codesSize, commitPoint,
                 outWords, frequencies, spaceIndices, outputTypes);
         if (DEBUG_DICT) {
             DUMP_RESULT(outWords, frequencies, 18 /* MAX_WORDS */, MAX_WORD_LENGTH_INTERNAL);
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
index ab238c8..fd9e770 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/dictionary.h
@@ -44,9 +44,9 @@
     Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler,
             int fullWordMultiplier, int maxWordLength, int maxWords, int maxPredictions);
 
-    int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
-            int *times, int *pointerIds, int *codes, int codesSize, int *prevWordChars,
-            int prevWordLength, int commitPoint, bool isGesture,
+    int getSuggestions(ProximityInfo *proximityInfo, void *traverseSession, int *xcoordinates,
+            int *ycoordinates, int *times, int *pointerIds, int *codes, int codesSize,
+            int *prevWordChars, int prevWordLength, int commitPoint, bool isGesture,
             bool useFullEditDistance, unsigned short *outWords,
             int *frequencies, int *spaceIndices, int *outputTypes);
 
@@ -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; }
+    const uint8_t *getDict() const { // required to release dictionary buffer
+        return mDict;
+    }
+    const uint8_t *getOffsetDict() const {
+        return mOffsetDict;
+    }
     int getDictSize() const { return mDictSize; }
     int getMmapFd() const { return mMmapFd; }
     int getDictBufAdjust() const { return mDictBufAdjust; }
@@ -67,7 +72,8 @@
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
-    const unsigned char *mDict;
+    const uint8_t *mDict;
+    const uint8_t *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..146eb80
--- /dev/null
+++ b/native/jni/src/geometry_utils.h
@@ -0,0 +1,98 @@
+/*
+ * 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 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 03c84b5..f8bfe7c 100644
--- a/native/jni/src/gesture/gesture_decoder_wrapper.h
+++ b/native/jni/src/gesture/gesture_decoder_wrapper.h
@@ -37,37 +37,15 @@
         delete mIncrementalDecoderInterface;
     }
 
-    int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
-            int *pointerIds, int *codes, int inputSize, int commitPoint,
+    int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
+            int *times, int *pointerIds, int *codes, int inputSize, int commitPoint,
             unsigned short *outWords, int *frequencies, int *outputIndices, int *outputTypes) {
         if (!mIncrementalDecoderInterface) {
             return 0;
         }
         return mIncrementalDecoderInterface->getSuggestions(
-                pInfo, inputXs, inputYs, times, pointerIds, codes, 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);
+                pInfo, traverseSession, inputXs, inputYs, times, pointerIds, codes,
+                inputSize, commitPoint, outWords, frequencies, outputIndices, outputTypes);
     }
 
     static void setGestureDecoderFactoryMethod(
@@ -76,7 +54,7 @@
     }
 
  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 6d2e273..04f0095 100644
--- a/native/jni/src/gesture/incremental_decoder_interface.h
+++ b/native/jni/src/gesture/incremental_decoder_interface.h
@@ -28,14 +28,14 @@
 
 class IncrementalDecoderInterface {
  public:
-    virtual int getSuggestions(ProximityInfo *pInfo, 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;
+    virtual int getSuggestions(ProximityInfo *pInfo, void *traverseSession,
+            int *inputXs, int *inputYs, int *times, int *pointerIds, int *codes,
+            int inputSize, int commitPoint, unsigned short *outWords, int *frequencies,
+            int *outputIndices, int *outputTypes) = 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 6980615..5cb2ee3 100644
--- a/native/jni/src/gesture/incremental_decoder_wrapper.h
+++ b/native/jni/src/gesture/incremental_decoder_wrapper.h
@@ -37,37 +37,15 @@
         delete mIncrementalDecoderInterface;
     }
 
-    int getSuggestions(ProximityInfo *pInfo, int *inputXs, int *inputYs, int *times,
-            int *pointerIds, int *codes, int inputSize, int commitPoint,
+    int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
+            int *times, int *pointerIds, int *codes, int inputSize, int commitPoint,
             unsigned short *outWords, int *frequencies, int *outputIndices, int *outputTypes) {
         if (!mIncrementalDecoderInterface) {
             return 0;
         }
         return mIncrementalDecoderInterface->getSuggestions(
-                pInfo, inputXs, inputYs, times, pointerIds, codes, 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);
+                pInfo, traverseSession, inputXs, inputYs, times, pointerIds, codes,
+                inputSize, commitPoint, outWords, frequencies, outputIndices, outputTypes);
     }
 
     static void setIncrementalDecoderFactoryMethod(
@@ -76,7 +54,7 @@
     }
 
  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 cee408d..1b9bac0 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -17,62 +17,76 @@
 #include <cassert>
 #include <cmath>
 #include <cstring>
-#include <string>
 
 #define LOG_TAG "LatinIME: proximity_info.cpp"
 
 #include "additional_proximity_chars.h"
 #include "char_utils.h"
 #include "defines.h"
+#include "geometry_utils.h"
+#include "jni.h"
 #include "proximity_info.h"
 
 namespace latinime {
 
-inline void copyOrFillZero(void *to, const void *from, size_t size) {
-    if (from) {
-        memcpy(to, from, size);
-    } else {
-        memset(to, 0, size);
+static inline void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray, jsize len,
+        jint *buffer) {
+    if (jArray && buffer) {
+        env->GetIntArrayRegion(jArray, 0, len, buffer);
+    } else if (buffer) {
+        memset(buffer, 0, len * sizeof(jint));
     }
 }
 
-ProximityInfo::ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
+static inline void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray, jsize len,
+        jfloat *buffer) {
+    if (jArray && buffer) {
+        env->GetFloatArrayRegion(jArray, 0, len, buffer);
+    } else if (buffer) {
+        memset(buffer, 0, len * sizeof(jfloat));
+    }
+}
+
+ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr, const int maxProximityCharsSize,
         const int keyboardWidth, const int keyboardHeight, const int gridWidth,
-        const int gridHeight, const int mostCommonKeyWidth,
-        const int32_t *proximityCharsArray, const int keyCount, const int32_t *keyXCoordinates,
-        const int32_t *keyYCoordinates, const int32_t *keyWidths, const int32_t *keyHeights,
-        const int32_t *keyCharCodes, const float *sweetSpotCenterXs, const float *sweetSpotCenterYs,
-        const float *sweetSpotRadii)
+        const int gridHeight, const int mostCommonKeyWidth, const jintArray proximityChars,
+        const int keyCount, const jintArray keyXCoordinates, const jintArray keyYCoordinates,
+        const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
+        const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
+        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),
           KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
           HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
                   && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
-                  && sweetSpotCenterYs && sweetSpotRadii),
-          mLocaleStr(localeStr) {
+                  && sweetSpotCenterYs && sweetSpotRadii) {
     const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
     if (DEBUG_PROXIMITY_INFO) {
         AKLOGI("Create proximity info array %d", proximityGridLength);
     }
+    const jsize localeCStrUtf8Length = env->GetStringUTFLength(localeJStr);
+    if (localeCStrUtf8Length >= MAX_LOCALE_STRING_LENGTH) {
+        AKLOGI("Locale string length too long: length=%d", localeCStrUtf8Length);
+        assert(false);
+    }
+    memset(mLocaleStr, 0, sizeof(mLocaleStr));
+    env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
     mProximityCharsArray = new int32_t[proximityGridLength];
-    memcpy(mProximityCharsArray, proximityCharsArray,
-            proximityGridLength * sizeof(mProximityCharsArray[0]));
-
-    copyOrFillZero(mKeyXCoordinates, keyXCoordinates, KEY_COUNT * sizeof(mKeyXCoordinates[0]));
-    copyOrFillZero(mKeyYCoordinates, keyYCoordinates, KEY_COUNT * sizeof(mKeyYCoordinates[0]));
-    copyOrFillZero(mKeyWidths, keyWidths, KEY_COUNT * sizeof(mKeyWidths[0]));
-    copyOrFillZero(mKeyHeights, keyHeights, KEY_COUNT * sizeof(mKeyHeights[0]));
-    copyOrFillZero(mKeyCharCodes, keyCharCodes, KEY_COUNT * sizeof(mKeyCharCodes[0]));
-    copyOrFillZero(mSweetSpotCenterXs, sweetSpotCenterXs,
-            KEY_COUNT * sizeof(mSweetSpotCenterXs[0]));
-    copyOrFillZero(mSweetSpotCenterYs, sweetSpotCenterYs,
-            KEY_COUNT * sizeof(mSweetSpotCenterYs[0]));
-    copyOrFillZero(mSweetSpotRadii, sweetSpotRadii, KEY_COUNT * sizeof(mSweetSpotRadii[0]));
-
+    safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityGridLength, mProximityCharsArray);
+    safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
+    safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
+    safeGetOrFillZeroIntArrayRegion(env, keyWidths, KEY_COUNT, mKeyWidths);
+    safeGetOrFillZeroIntArrayRegion(env, keyHeights, KEY_COUNT, mKeyHeights);
+    safeGetOrFillZeroIntArrayRegion(env, keyCharCodes, KEY_COUNT, mKeyCharCodes);
+    safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterXs, KEY_COUNT, mSweetSpotCenterXs);
+    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,
@@ -121,6 +135,21 @@
     return false;
 }
 
+static inline float getNormalizedSquaredDistanceFloat(float x1, float y1, float x2, float y2,
+        float scale) {
+    return squareFloat((x1 - x2) / scale) + squareFloat((y1 - y2) / scale);
+}
+
+float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloat(
+        const int keyId, const int x, const int y) const {
+    const float centerX = static_cast<float>(getKeyCenterXOfIdG(keyId));
+    const float centerY = static_cast<float>(getKeyCenterYOfIdG(keyId));
+    const float touchX = static_cast<float>(x);
+    const float touchY = static_cast<float>(y);
+    const float keyWidth = static_cast<float>(getMostCommonKeyWidth());
+    return getNormalizedSquaredDistanceFloat(centerX, centerY, touchX, touchY, keyWidth);
+}
+
 int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
     if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
     const int left = mKeyXCoordinates[keyId];
@@ -160,7 +189,7 @@
             }
         }
         const int additionalProximitySize =
-                AdditionalProximityChars::getAdditionalCharsSize(&mLocaleStr, primaryKey);
+                AdditionalProximityChars::getAdditionalCharsSize(mLocaleStr, primaryKey);
         if (additionalProximitySize > 0) {
             inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
             if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
@@ -171,7 +200,7 @@
             }
 
             const int32_t *additionalProximityChars =
-                    AdditionalProximityChars::getAdditionalChars(&mLocaleStr, primaryKey);
+                    AdditionalProximityChars::getAdditionalChars(mLocaleStr, primaryKey);
             for (int j = 0; j < additionalProximitySize; ++j) {
                 const int32_t ac = additionalProximityChars[j];
                 int k = 0;
@@ -211,24 +240,65 @@
     return mCodeToKeyIndex[baseLowerC];
 }
 
-// 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));
+int ProximityInfo::getKeyCode(const int keyIndex) const {
+    if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
+        return NOT_AN_INDEX;
+    }
+    return mKeyToCodeIndexG[keyIndex];
+}
 
+void ProximityInfo::initializeG() {
+    // TODO: Optimize
     for (int i = 0; i < KEY_COUNT; ++i) {
         const int code = mKeyCharCodes[i];
         const int lowerCode = toBaseLowerCase(code);
-        centerXs[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
-        centerYs[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
-        codeToKeyIndex[code] = i;
+        mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
+        mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
         if (code != lowerCode && lowerCode >= 0 && lowerCode <= MAX_CHAR_CODE) {
-            codeToKeyIndex[lowerCode] = i;
-            keyToCodeIndex[i] = lowerCode;
+            mCodeToKeyIndex[lowerCode] = i;
+            mKeyToCodeIndexG[i] = lowerCode;
         } else {
-            keyToCodeIndex[i] = code;
+            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;
 }
 } // namespace latinime
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index abd07dd..8a407e7 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -18,9 +18,9 @@
 #define LATINIME_PROXIMITY_INFO_H
 
 #include <stdint.h>
-#include <string>
 
 #include "defines.h"
+#include "jni.h"
 
 namespace latinime {
 
@@ -28,16 +28,18 @@
 
 class ProximityInfo {
  public:
-    ProximityInfo(const std::string localeStr, const int maxProximityCharsSize,
+    ProximityInfo(JNIEnv *env, const jstring localeJStr, const int maxProximityCharsSize,
             const int keyboardWidth, const int keyboardHeight, const int gridWidth,
-            const int gridHeight, const int mostCommonkeyWidth,
-            const int32_t *proximityCharsArray, const int keyCount, const int32_t *keyXCoordinates,
-            const int32_t *keyYCoordinates, const int32_t *keyWidths, const int32_t *keyHeights,
-            const int32_t *keyCharCodes, const float *sweetSpotCenterXs,
-            const float *sweetSpotCenterYs, const float *sweetSpotRadii);
+            const int gridHeight, const int mostCommonKeyWidth, const jintArray proximityChars,
+            const int keyCount, const jintArray keyXCoordinates, const jintArray keyYCoordinates,
+            const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
+            const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
+            const jfloatArray sweetSpotRadii);
     ~ProximityInfo();
     bool hasSpaceProximity(const int x, const int y) const;
     int getNormalizedSquaredDistance(const int inputIndex, const int proximityIndex) const;
+    float getNormalizedSquaredDistanceFromCenterFloat(
+            const int keyId, const int x, const int y) const;
     bool sameAsTyped(const unsigned short *word, int length) const;
     int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
     bool isOnKey(const int keyId, const int x, const int y) const {
@@ -49,6 +51,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,11 +73,15 @@
         return HAS_TOUCH_POSITION_CORRECTION_DATA;
     }
 
+    int getMostCommonKeyWidth() const {
+        return MOST_COMMON_KEY_WIDTH;
+    }
+
     int getMostCommonKeyWidthSquare() const {
         return MOST_COMMON_KEY_WIDTH_SQUARE;
     }
 
-    std::string getLocaleStr() const {
+    const char *getLocaleStr() const {
         return mLocaleStr;
     }
 
@@ -98,6 +105,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 +126,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,12 +137,13 @@
     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;
     const int KEY_COUNT;
     const bool HAS_TOUCH_POSITION_CORRECTION_DATA;
-    const std::string mLocaleStr;
+    char mLocaleStr[MAX_LOCALE_STRING_LENGTH];
     int32_t *mProximityCharsArray;
     int32_t mKeyXCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
     int32_t mKeyYCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
@@ -139,6 +154,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..1e4e00a 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,12 @@
                 continue;
             }
             const unsigned int wordLength = sw->mWordLength;
-            char *targetAdr = (char*) outputChars + i * MAX_WORD_LENGTH * sizeof(short);
+            unsigned short *targetAddress = outputChars + i * MAX_WORD_LENGTH;
             frequencies[i] = sw->mScore;
-            memcpy(targetAdr, sw->mWord, (wordLength) * sizeof(short));
+            outputTypes[i] = sw->mType;
+            memcpy(targetAddress, sw->mWord, wordLength * sizeof(unsigned short));
             if (wordLength < MAX_WORD_LENGTH) {
-                ((unsigned short*) targetAdr)[wordLength] = 0;
+                targetAddress[wordLength] = 0;
             }
             sw->mUsed = false;
         }
@@ -191,10 +194,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];
             }
         }
@@ -219,7 +222,7 @@
                 before, beforeLength, word, wordLength, score);
     }
 
-    typedef std::priority_queue<SuggestedWord*, std::vector<SuggestedWord*>,
+    typedef std::priority_queue<SuggestedWord *, std::vector<SuggestedWord *>,
             wordComparator> Suggestions;
     Suggestions mSuggestions;
     const unsigned int MAX_WORDS;
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
index 99fbc96..8fed28f 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueueTests.java
@@ -19,7 +19,7 @@
 import android.test.AndroidTestCase;
 
 public class PointerTrackerQueueTests extends AndroidTestCase {
-    public static class Element implements PointerTrackerQueue.ElementActions {
+    public static class Element implements PointerTrackerQueue.Element {
         public static int sPhantomUpCount;
         public static final long NOT_HAPPENED = -1;
 
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index e9c11ac..df8cb10 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -24,7 +24,7 @@
         $(filter-out $(addprefix %/, $(notdir $(LOCAL_TOOL_SRC_FILES))), $(LOCAL_MAIN_SRC_FILES)) \
         $(call all-java-files-under,tests)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
-LOCAL_MODULE := dicttool
+LOCAL_MODULE := dicttool_aosp
 LOCAL_JAVA_LIBRARIES := junit
 LOCAL_MODULE_TAGS := eng
 
diff --git a/tools/dicttool/etc/Android.mk b/tools/dicttool/etc/Android.mk
index 03d4a96..8952827 100644
--- a/tools/dicttool/etc/Android.mk
+++ b/tools/dicttool/etc/Android.mk
@@ -16,5 +16,5 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := eng
-LOCAL_PREBUILT_EXECUTABLES := dicttool
+LOCAL_PREBUILT_EXECUTABLES := dicttool_aosp makedict_aosp
 include $(BUILD_HOST_PREBUILT)
diff --git a/tools/dicttool/etc/dicttool b/tools/dicttool/etc/dicttool_aosp
similarity index 98%
rename from tools/dicttool/etc/dicttool
rename to tools/dicttool/etc/dicttool_aosp
index 8a39694..a4879a2 100755
--- a/tools/dicttool/etc/dicttool
+++ b/tools/dicttool/etc/dicttool_aosp
@@ -33,7 +33,7 @@
 prog="${progdir}"/`basename "${prog}"`
 cd "${oldwd}"
 
-jarfile=dicttool.jar
+jarfile=dicttool_aosp.jar
 frameworkdir="$progdir"
 if [ ! -r "$frameworkdir/$jarfile" ]
 then
diff --git a/tools/makedict/etc/Android.mk b/tools/dicttool/etc/makedict_aosp
old mode 100644
new mode 100755
similarity index 63%
rename from tools/makedict/etc/Android.mk
rename to tools/dicttool/etc/makedict_aosp
index 1b7d7cf..095c505
--- a/tools/makedict/etc/Android.mk
+++ b/tools/dicttool/etc/makedict_aosp
@@ -1,10 +1,11 @@
-# Copyright (C) 2011 The Android Open Source Project
+#!/bin/sh
+# 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
+#     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,
@@ -12,8 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_PREBUILT_EXECUTABLES := makedict
-include $(BUILD_HOST_PREBUILT)
+# Dicttool supports making the dictionary using the 'makedict' command and
+# the same arguments that the old 'makedict' command used to accept.
+dicttool_aosp makedict $@
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java
new file mode 100644
index 0000000..8d4eb75
--- /dev/null
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java
@@ -0,0 +1,22 @@
+/**
+ * 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.dicttool;
+
+public class AdditionalCommandList {
+    public static void populate() {
+    }
+}
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/CommandList.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/CommandList.java
new file mode 100644
index 0000000..d16b069
--- /dev/null
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/CommandList.java
@@ -0,0 +1,26 @@
+/**
+ * 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.dicttool;
+
+public class CommandList {
+    public static void populate() {
+        Dicttool.addCommand("info", Info.class);
+        Dicttool.addCommand("compress", Compress.Compressor.class);
+        Dicttool.addCommand("uncompress", Compress.Uncompressor.class);
+        Dicttool.addCommand("makedict", Makedict.class);
+    }
+}
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
index a76ec50..3cb0a12 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
@@ -46,46 +46,52 @@
 
     static public class Compressor extends Dicttool.Command {
         public static final String COMMAND = "compress";
-        private static final String SUFFIX = ".compressed";
+        public static final String STDIN_OR_STDOUT = "-";
 
         public Compressor() {
         }
 
         public String getHelp() {
-            return "compress <filename>: Compresses a file using gzip compression";
+            return COMMAND + " <src_filename> <dst_filename>: "
+                    + "Compresses a file using gzip compression";
         }
 
         public void run() throws IOException {
-            if (mArgs.length < 1) {
-                throw new RuntimeException("Not enough arguments for command " + COMMAND);
+            if (mArgs.length > 2) {
+                throw new RuntimeException("Too many arguments for command " + COMMAND);
             }
-            final String inFilename = mArgs[0];
-            final String outFilename = inFilename + SUFFIX;
-            final FileInputStream input = new FileInputStream(new File(inFilename));
-            final FileOutputStream output = new FileOutputStream(new File(outFilename));
+            final String inFilename = mArgs.length >= 1 ? mArgs[0] : STDIN_OR_STDOUT;
+            final String outFilename = mArgs.length >= 2 ? mArgs[1] : STDIN_OR_STDOUT;
+            final InputStream input = inFilename.equals(STDIN_OR_STDOUT) ? System.in
+                    : new FileInputStream(new File(inFilename));
+            final OutputStream output = outFilename.equals(STDIN_OR_STDOUT) ? System.out
+                    : new FileOutputStream(new File(outFilename));
             copy(input, new GZIPOutputStream(output));
         }
     }
 
     static public class Uncompressor extends Dicttool.Command {
         public static final String COMMAND = "uncompress";
-        private static final String SUFFIX = ".uncompressed";
+        public static final String STDIN_OR_STDOUT = "-";
 
         public Uncompressor() {
         }
 
         public String getHelp() {
-            return "uncompress <filename>: Uncompresses a file compressed with gzip compression";
+            return COMMAND + " <src_filename> <dst_filename>: "
+                    + "Uncompresses a file compressed with gzip compression";
         }
 
         public void run() throws IOException {
-            if (mArgs.length < 1) {
-                throw new RuntimeException("Not enough arguments for command " + COMMAND);
+            if (mArgs.length > 2) {
+                throw new RuntimeException("Too many arguments for command " + COMMAND);
             }
-            final String inFilename = mArgs[0];
-            final String outFilename = inFilename + SUFFIX;
-            final FileInputStream input = new FileInputStream(new File(inFilename));
-            final FileOutputStream output = new FileOutputStream(new File(outFilename));
+            final String inFilename = mArgs.length >= 1 ? mArgs[0] : STDIN_OR_STDOUT;
+            final String outFilename = mArgs.length >= 2 ? mArgs[1] : STDIN_OR_STDOUT;
+            final InputStream input = inFilename.equals(STDIN_OR_STDOUT) ? System.in
+                    : new FileInputStream(new File(inFilename));
+            final OutputStream output = outFilename.equals(STDIN_OR_STDOUT) ? System.out
+                    : new FileOutputStream(new File(outFilename));
             copy(new GZIPInputStream(input), output);
         }
     }
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/Dicttool.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
index c14ce7b..bf417fb 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
@@ -32,10 +32,11 @@
     static HashMap<String, Class<? extends Command>> sCommands =
             new HashMap<String, Class<? extends Command>>();
     static {
-        sCommands.put("info", Info.class);
-        sCommands.put("compress", Compress.Compressor.class);
-        sCommands.put("uncompress", Compress.Uncompressor.class);
-        sCommands.put("makedict", Makedict.class);
+        CommandList.populate();
+        AdditionalCommandList.populate();
+    }
+    public static void addCommand(final String commandName, final Class<? extends Command> cls) {
+        sCommands.put(commandName, cls);
     }
 
     private static Command getCommandInstance(final String commandName) {
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.
diff --git a/tools/makedict/Android.mk b/tools/makedict/Android.mk
deleted file mode 100644
index cac3a83..0000000
--- a/tools/makedict/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-MAKEDICT_CORE_SOURCE_DIRECTORY := ../../java/src/com/android/inputmethod/latin/makedict
-
-LOCAL_MAIN_SRC_FILES := $(call all-java-files-under,$(MAKEDICT_CORE_SOURCE_DIRECTORY))
-LOCAL_TOOL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_SRC_FILES := $(LOCAL_TOOL_SRC_FILES)
-LOCAL_JAR_MANIFEST := etc/manifest.txt
-LOCAL_MODULE := makedict
-LOCAL_JAVA_LIBRARIES := junit
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-include $(LOCAL_PATH)/etc/Android.mk
diff --git a/tools/makedict/etc/makedict b/tools/makedict/etc/makedict
deleted file mode 100755
index 7c1c02e..0000000
--- a/tools/makedict/etc/makedict
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/bin/sh
-# Copyright 2011, The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Set up prog to be the path of this script, including following symlinks,
-# and set up progdir to be the fully-qualified pathname of its directory.
-prog="$0"
-while [ -h "${prog}" ]; do
-    newProg=`/bin/ls -ld "${prog}"`
-    newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
-    if expr "x${newProg}" : 'x/' >/dev/null; then
-        prog="${newProg}"
-    else
-        progdir=`dirname "${prog}"`
-        prog="${progdir}/${newProg}"
-    fi
-done
-oldwd=`pwd`
-progdir=`dirname "${prog}"`
-cd "${progdir}"
-progdir=`pwd`
-prog="${progdir}"/`basename "${prog}"`
-cd "${oldwd}"
-
-jarfile=makedict.jar
-frameworkdir="$progdir"
-if [ ! -r "$frameworkdir/$jarfile" ]
-then
-    frameworkdir=`dirname "$progdir"`/tools/lib
-    libdir=`dirname "$progdir"`/tools/lib
-fi
-if [ ! -r "$frameworkdir/$jarfile" ]
-then
-    frameworkdir=`dirname "$progdir"`/framework
-    libdir=`dirname "$progdir"`/lib
-fi
-if [ ! -r "$frameworkdir/$jarfile" ]
-then
-    echo `basename "$prog"`": can't find $jarfile"
-    exit 1
-fi
-
-if [ "$OSTYPE" = "cygwin" ] ; then
-    jarpath=`cygpath -w  "$frameworkdir/$jarfile"`
-    progdir=`cygpath -w  "$progdir"`
-else
-    jarpath="$frameworkdir/$jarfile"
-fi
-
-# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
-# might need more memory, e.g. -Xmx128M
-exec java -ea -Djava.ext.dirs="$frameworkdir" -jar "$jarpath" "$@"
diff --git a/tools/makedict/etc/manifest.txt b/tools/makedict/etc/manifest.txt
deleted file mode 100644
index 4f085e7..0000000
--- a/tools/makedict/etc/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: com.android.inputmethod.latin.makedict.DictionaryMaker