merge in jb-mr1-release history after reset to jb-mr1-dev
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 4975d65..cced45d 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -131,6 +131,12 @@
         <attr name="gestureFloatingPreviewTextConnectorWidth" format="dimension" />
         <!-- Delay after gesture input and gesture floating preview text dismissing in millisecond -->
         <attr name="gestureFloatingPreviewTextLingerTimeout" format="integer" />
+        <!-- Delay after gesture trail starts fading out in millisecond. -->
+        <attr name="gesturePreviewTrailFadeoutStartDelay" format="integer" />
+        <!-- Duration while gesture preview trail is fading out in millisecond. -->
+        <attr name="gesturePreviewTrailFadeoutDuration" format="integer" />
+        <!-- Interval of updating gesture preview trail in millisecond. -->
+        <attr name="gesturePreviewTrailUpdateInterval" format="integer" />
         <attr name="gesturePreviewTrailColor" format="color" />
         <attr name="gesturePreviewTrailWidth" format="dimension" />
     </declare-styleable>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 54a6687..8e2d43e 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -50,6 +50,9 @@
     -->
     <integer name="config_key_preview_linger_timeout">70</integer>
     <integer name="config_gesture_floating_preview_text_linger_timeout">200</integer>
+    <integer name="config_gesture_preview_trail_fadeout_start_delay">100</integer>
+    <integer name="config_gesture_preview_trail_fadeout_duration">800</integer>
+    <integer name="config_gesture_preview_trail_update_interval">20</integer>
     <!--
          Configuration for MainKeyboardView
     -->
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 2569f23..8f6b647 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -22,5 +22,6 @@
         <!-- Build.HARDWARE,duration_in_milliseconds -->
         <item>herring,5</item>
         <item>tuna,5</item>
+        <item>mako,20</item>
     </string-array>
 </resources>
diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml
index 3b433e4..dd86d6c 100644
--- a/java/res/values/keypress-volumes.xml
+++ b/java/res/values/keypress-volumes.xml
@@ -24,5 +24,6 @@
         <item>tuna,0.5</item>
         <item>stingray,0.4</item>
         <item>grouper,0.3</item>
+        <item>mako,0.3</item>
     </string-array>
 </resources>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index ae67c43..e5e7fed 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -78,6 +78,9 @@
         <item name="gestureFloatingPreviewTextConnectorColor">@android:color/white</item>
         <item name="gestureFloatingPreviewTextConnectorWidth">@dimen/gesture_floating_preview_text_connector_width</item>
         <item name="gestureFloatingPreviewTextLingerTimeout">@integer/config_gesture_floating_preview_text_linger_timeout</item>
+        <item name="gesturePreviewTrailFadeoutStartDelay">@integer/config_gesture_preview_trail_fadeout_start_delay</item>
+        <item name="gesturePreviewTrailFadeoutDuration">@integer/config_gesture_preview_trail_fadeout_duration</item>
+        <item name="gesturePreviewTrailUpdateInterval">@integer/config_gesture_preview_trail_update_interval</item>
         <item name="gesturePreviewTrailColor">@android:color/holo_blue_light</item>
         <item name="gesturePreviewTrailWidth">@dimen/gesture_preview_trail_width</item>
         <!-- Common attributes of MainKeyboardView -->
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 67f37a7..b5b3ef6 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -17,14 +17,13 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.keyboard.internal.GestureStroke;
+import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewTrail;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.InputPointers;
@@ -211,7 +210,7 @@
     private static final KeyboardActionListener EMPTY_LISTENER =
             new KeyboardActionListener.Adapter();
 
-    private final GestureStroke mGestureStroke;
+    private final GestureStrokeWithPreviewTrail mGestureStrokeWithPreviewTrail;
 
     public static void init(boolean hasDistinctMultitouch,
             boolean needsPhantomSuddenMoveEventHack) {
@@ -297,7 +296,8 @@
         final int trackersSize = sTrackers.size();
         for (int i = 0; i < trackersSize; ++i) {
             final PointerTracker tracker = sTrackers.get(i);
-            tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers);
+            tracker.mGestureStrokeWithPreviewTrail.appendIncrementalBatchPoints(
+                    sAggregratedPointers);
         }
         return sAggregratedPointers;
     }
@@ -308,7 +308,7 @@
         final int trackersSize = sTrackers.size();
         for (int i = 0; i < trackersSize; ++i) {
             final PointerTracker tracker = sTrackers.get(i);
-            tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers);
+            tracker.mGestureStrokeWithPreviewTrail.appendAllBatchPoints(sAggregratedPointers);
         }
         return sAggregratedPointers;
     }
@@ -319,7 +319,7 @@
         final int trackersSize = sTrackers.size();
         for (int i = 0; i < trackersSize; ++i) {
             final PointerTracker tracker = sTrackers.get(i);
-            tracker.mGestureStroke.reset();
+            tracker.mGestureStrokeWithPreviewTrail.reset();
         }
         sAggregratedPointers.reset();
     }
@@ -329,7 +329,7 @@
             throw new NullPointerException();
         }
         mPointerId = id;
-        mGestureStroke = new GestureStroke(id);
+        mGestureStrokeWithPreviewTrail = new GestureStrokeWithPreviewTrail(id);
         setKeyDetectorInner(handler.getKeyDetector());
         mListener = handler.getKeyboardActionListener();
         mDrawingProxy = handler.getDrawingProxy();
@@ -429,7 +429,7 @@
         mKeyDetector = keyDetector;
         mKeyboard = keyDetector.getKeyboard();
         mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard();
-        mGestureStroke.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
+        mGestureStrokeWithPreviewTrail.setGestureSampleLength(mKeyboard.mMostCommonKeyWidth);
         final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY);
         if (newKey != mCurrentKey) {
             if (mDrawingProxy != null) {
@@ -539,10 +539,8 @@
         mDrawingProxy.invalidateKey(key);
     }
 
-    public void drawGestureTrail(final Canvas canvas, final Paint paint) {
-        if (mInGesture) {
-            mGestureStroke.drawGestureTrail(canvas, paint);
-        }
+    public GestureStrokeWithPreviewTrail getGestureStrokeWithPreviewTrail() {
+        return mGestureStrokeWithPreviewTrail;
     }
 
     public int getLastX() {
@@ -692,7 +690,7 @@
                 mIsPossibleGesture = true;
                 // TODO: pointer times should be relative to first down even in entire batch input
                 // instead of resetting to 0 for each new down event.
-                mGestureStroke.addPoint(x, y, 0, false);
+                mGestureStrokeWithPreviewTrail.addPoint(x, y, 0, false);
             }
         }
     }
@@ -733,7 +731,7 @@
             final long eventTime, final boolean isHistorical, final Key key) {
         final int gestureTime = (int)(eventTime - tracker.getDownTime());
         if (sShouldHandleGesture && mIsPossibleGesture) {
-            final GestureStroke stroke = mGestureStroke;
+            final GestureStroke stroke = mGestureStrokeWithPreviewTrail;
             stroke.addPoint(x, y, gestureTime, isHistorical);
             if (!mInGesture && stroke.isStartOfAGesture()) {
                 startBatchInput();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
new file mode 100644
index 0000000..e814d80
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -0,0 +1,166 @@
+/*
+ * 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.keyboard.internal;
+
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.SystemClock;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResizableIntArray;
+
+class GesturePreviewTrail {
+    private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewTrail.PREVIEW_CAPACITY;
+
+    private final GesturePreviewTrailParams mPreviewParams;
+    private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+    private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
+    private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
+    private int mCurrentStrokeId = -1;
+    private long mCurrentDownTime;
+    private int mTrailStartIndex;
+
+    // Use this value as imaginary zero because x-coordinates may be zero.
+    private static final int DOWN_EVENT_MARKER = -128;
+
+    static class GesturePreviewTrailParams {
+        public final int mFadeoutStartDelay;
+        public final int mFadeoutDuration;
+        public final int mUpdateInterval;
+
+        public GesturePreviewTrailParams(final TypedArray keyboardViewAttr) {
+            mFadeoutStartDelay = keyboardViewAttr.getInt(
+                    R.styleable.KeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
+            mFadeoutDuration = keyboardViewAttr.getInt(
+                    R.styleable.KeyboardView_gesturePreviewTrailFadeoutDuration, 0);
+            mUpdateInterval = keyboardViewAttr.getInt(
+                    R.styleable.KeyboardView_gesturePreviewTrailUpdateInterval, 0);
+        }
+    }
+
+    public GesturePreviewTrail(final GesturePreviewTrailParams params) {
+        mPreviewParams = params;
+    }
+
+    private static int markAsDownEvent(final int xCoord) {
+        return DOWN_EVENT_MARKER - xCoord;
+    }
+
+    private static boolean isDownEventXCoord(final int xCoordOrMark) {
+        return xCoordOrMark <= DOWN_EVENT_MARKER;
+    }
+
+    private static int getXCoordValue(final int xCoordOrMark) {
+        return isDownEventXCoord(xCoordOrMark)
+                ? DOWN_EVENT_MARKER - xCoordOrMark : xCoordOrMark;
+    }
+
+    public void addStroke(final GestureStrokeWithPreviewTrail stroke, final long downTime) {
+        final int strokeId = stroke.getGestureStrokeId();
+        final boolean isNewStroke = strokeId != mCurrentStrokeId;
+        final int trailSize = mEventTimes.getLength();
+        stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
+        final int newTrailSize = mEventTimes.getLength();
+        if (stroke.getGestureStrokePreviewSize() == 0) {
+            return;
+        }
+        if (isNewStroke) {
+            final int elapsedTime = (int)(downTime - mCurrentDownTime);
+            final int[] eventTimes = mEventTimes.getPrimitiveArray();
+            for (int i = mTrailStartIndex; i < trailSize; i++) {
+                eventTimes[i] -= elapsedTime;
+            }
+
+            if (newTrailSize > trailSize) {
+                final int[] xCoords = mXCoordinates.getPrimitiveArray();
+                xCoords[trailSize] = markAsDownEvent(xCoords[trailSize]);
+            }
+            mCurrentDownTime = downTime;
+            mCurrentStrokeId = strokeId;
+        }
+    }
+
+    private int getAlpha(final int elapsedTime) {
+        if (elapsedTime < mPreviewParams.mFadeoutStartDelay) {
+            return Constants.Color.ALPHA_OPAQUE;
+        }
+        final int decreasingAlpha = Constants.Color.ALPHA_OPAQUE
+                * (elapsedTime - mPreviewParams.mFadeoutStartDelay)
+                / mPreviewParams.mFadeoutDuration;
+        return Constants.Color.ALPHA_OPAQUE - decreasingAlpha;
+    }
+
+    /**
+     * Draw gesture preview trail
+     * @param canvas The canvas to draw the gesture preview trail
+     * @param paint The paint object to be used to draw the gesture preview trail
+     * @return true if some gesture preview trails remain to be drawn
+     */
+    public boolean drawGestureTrail(final Canvas canvas, final Paint paint) {
+        final int trailSize = mEventTimes.getLength();
+        if (trailSize == 0) {
+            return false;
+        }
+
+        final int[] eventTimes = mEventTimes.getPrimitiveArray();
+        final int[] xCoords = mXCoordinates.getPrimitiveArray();
+        final int[] yCoords = mYCoordinates.getPrimitiveArray();
+        final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentDownTime);
+        final int lingeringDuration = mPreviewParams.mFadeoutStartDelay
+                + mPreviewParams.mFadeoutDuration;
+        int startIndex;
+        for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) {
+            final int elapsedTime = sinceDown - eventTimes[startIndex];
+            // Skip too old trail points.
+            if (elapsedTime < lingeringDuration) {
+                break;
+            }
+        }
+        mTrailStartIndex = startIndex;
+
+        if (startIndex < trailSize) {
+            int lastX = getXCoordValue(xCoords[startIndex]);
+            int lastY = yCoords[startIndex];
+            for (int i = startIndex + 1; i < trailSize - 1; i++) {
+                final int x = xCoords[i];
+                final int y = yCoords[i];
+                final int elapsedTime = sinceDown - eventTimes[i];
+                // Draw trail line only when the current point isn't a down point.
+                if (!isDownEventXCoord(x)) {
+                    paint.setAlpha(getAlpha(elapsedTime));
+                    canvas.drawLine(lastX, lastY, x, y, paint);
+                }
+                lastX = getXCoordValue(x);
+                lastY = y;
+            }
+        }
+
+        final int newSize = trailSize - startIndex;
+        if (newSize < startIndex) {
+            mTrailStartIndex = 0;
+            if (newSize > 0) {
+                System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
+                System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
+                System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+            }
+            mEventTimes.setLength(newSize);
+            mXCoordinates.setLength(newSize);
+            mYCoordinates.setLength(newSize);
+        }
+        return newSize > 0;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 79e977a..292842d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -14,10 +14,6 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import android.graphics.Canvas;
-import android.graphics.Paint;
-
-import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.ResizableIntArray;
 
@@ -48,13 +44,8 @@
 
     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;
-    private static final int DRAWING_GESTURE_FADE_RATE = 6;
-
-    public GestureStroke(int pointerId) {
+    public GestureStroke(final int pointerId) {
         mPointerId = pointerId;
-        reset();
     }
 
     public void setGestureSampleLength(final int keyWidth) {
@@ -158,7 +149,7 @@
         if (dx == 0 && dy == 0) return 0;
         // 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);
+        return (float)Math.atan2(dy, dx);
     }
 
     private static float getAngleDiff(final float a1, final float a2) {
@@ -168,20 +159,4 @@
         }
         return diff;
     }
-
-    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();
-        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);
-            if (size - i > DRAWING_GESTURE_FADE_START) {
-                alpha -= DRAWING_GESTURE_FADE_RATE;
-            }
-            canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint);
-        }
-    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
new file mode 100644
index 0000000..6c1a9bc
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewTrail.java
@@ -0,0 +1,70 @@
+/*
+ * 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.keyboard.internal;
+
+import com.android.inputmethod.latin.ResizableIntArray;
+
+public class GestureStrokeWithPreviewTrail extends GestureStroke {
+    public static final int PREVIEW_CAPACITY = 256;
+
+    private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
+    private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
+    private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
+
+    private int mStrokeId;
+    private int mLastPreviewSize;
+
+    public GestureStrokeWithPreviewTrail(final int pointerId) {
+        super(pointerId);
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        mStrokeId++;
+        mLastPreviewSize = 0;
+        mPreviewEventTimes.setLength(0);
+        mPreviewXCoordinates.setLength(0);
+        mPreviewYCoordinates.setLength(0);
+    }
+
+    public int getGestureStrokeId() {
+        return mStrokeId;
+    }
+
+    public int getGestureStrokePreviewSize() {
+        return mPreviewEventTimes.getLength();
+    }
+
+    @Override
+    public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
+        super.addPoint(x, y, time, isHistorical);
+        mPreviewEventTimes.add(time);
+        mPreviewXCoordinates.add(x);
+        mPreviewYCoordinates.add(y);
+    }
+
+    public void appendPreviewStroke(final ResizableIntArray eventTimes,
+            final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+        final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
+        if (length <= 0) {
+            return;
+        }
+        eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
+        xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
+        yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
+        mLastPreviewSize = mPreviewEventTimes.getLength();
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 3f33aee..269b202 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -28,6 +28,7 @@
 import android.widget.RelativeLayout;
 
 import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.GesturePreviewTrailParams;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
@@ -48,7 +49,9 @@
     private int mXOrigin;
     private int mYOrigin;
 
-    private final SparseArray<PointerTracker> mPointers = CollectionUtils.newSparseArray();
+    private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
+            CollectionUtils.newSparseArray();
+    private final GesturePreviewTrailParams mGesturePreviewTrailParams;
 
     private String mGestureFloatingPreviewText;
     private int mLastPointerX;
@@ -57,23 +60,31 @@
     private boolean mDrawsGesturePreviewTrail;
     private boolean mDrawsGestureFloatingPreviewText;
 
-    private final DrawingHandler mDrawingHandler = new DrawingHandler(this);
+    private final DrawingHandler mDrawingHandler;
 
     private static class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
         private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
+        private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
 
-        public DrawingHandler(PreviewPlacerView outerInstance) {
+        private final GesturePreviewTrailParams mGesturePreviewTrailParams;
+
+        public DrawingHandler(final PreviewPlacerView outerInstance,
+                final GesturePreviewTrailParams gesturePreviewTrailParams) {
             super(outerInstance);
+            mGesturePreviewTrailParams = gesturePreviewTrailParams;
         }
 
         @Override
-        public void handleMessage(Message msg) {
+        public void handleMessage(final Message msg) {
             final PreviewPlacerView placerView = getOuterInstance();
             if (placerView == null) return;
             switch (msg.what) {
             case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
                 placerView.setGestureFloatingPreviewText(null);
                 break;
+            case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
+                placerView.invalidate();
+                break;
             }
         }
 
@@ -89,16 +100,27 @@
                     placerView.mGestureFloatingPreviewTextLingerTimeout);
         }
 
+        private void cancelUpdateGestureTrailPreview() {
+            removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
+        }
+
+        public void postUpdateGestureTrailPreview() {
+            cancelUpdateGestureTrailPreview();
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
+                    mGesturePreviewTrailParams.mUpdateInterval);
+        }
+
         public void cancelAllMessages() {
             cancelDismissGestureFloatingPreviewText();
+            cancelUpdateGestureTrailPreview();
         }
     }
 
-    public PreviewPlacerView(Context context, AttributeSet attrs) {
+    public PreviewPlacerView(final Context context, final AttributeSet attrs) {
         this(context, attrs, R.attr.keyboardViewStyle);
     }
 
-    public PreviewPlacerView(Context context, AttributeSet attrs, int defStyle) {
+    public PreviewPlacerView(final Context context, final AttributeSet attrs, final int defStyle) {
         super(context);
         setWillNotDraw(false);
 
@@ -128,8 +150,11 @@
                 R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
         final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
                 R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
+        mGesturePreviewTrailParams = new GesturePreviewTrailParams(keyboardViewAttr);
         keyboardViewAttr.recycle();
 
+        mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
+
         mGesturePaint = new Paint();
         mGesturePaint.setAntiAlias(true);
         mGesturePaint.setStyle(Paint.Style.STROKE);
@@ -144,21 +169,28 @@
         mTextPaint.setTextSize(gestureFloatingPreviewTextSize);
     }
 
-    public void setOrigin(int x, int y) {
+    public void setOrigin(final int x, final int y) {
         mXOrigin = x;
         mYOrigin = y;
     }
 
-    public void setGesturePreviewMode(boolean drawsGesturePreviewTrail,
-            boolean drawsGestureFloatingPreviewText) {
+    public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail,
+            final boolean drawsGestureFloatingPreviewText) {
         mDrawsGesturePreviewTrail = drawsGesturePreviewTrail;
         mDrawsGestureFloatingPreviewText = drawsGestureFloatingPreviewText;
     }
 
-    public void invalidatePointer(PointerTracker tracker) {
-        synchronized (mPointers) {
-            mPointers.put(tracker.mPointerId, tracker);
+    public void invalidatePointer(final PointerTracker tracker) {
+        GesturePreviewTrail trail;
+        synchronized (mGesturePreviewTrails) {
+            trail = mGesturePreviewTrails.get(tracker.mPointerId);
+            if (trail == null) {
+                trail = new GesturePreviewTrail(mGesturePreviewTrailParams);
+                mGesturePreviewTrails.put(tracker.mPointerId, trail);
+            }
         }
+        trail.addStroke(tracker.getGestureStrokeWithPreviewTrail(), tracker.getDownTime());
+
         mLastPointerX = tracker.getLastX();
         mLastPointerY = tracker.getLastY();
         // TODO: Should narrow the invalidate region.
@@ -166,17 +198,23 @@
     }
 
     @Override
-    public void onDraw(Canvas canvas) {
+    public void onDraw(final Canvas canvas) {
         super.onDraw(canvas);
         canvas.translate(mXOrigin, mYOrigin);
         if (mDrawsGesturePreviewTrail) {
-            synchronized (mPointers) {
-                final int trackerCount = mPointers.size();
-                for (int index = 0; index < trackerCount; index++) {
-                    final PointerTracker tracker = mPointers.valueAt(index);
-                    tracker.drawGestureTrail(canvas, mGesturePaint);
+            boolean needsUpdatingGesturePreviewTrail = false;
+            synchronized (mGesturePreviewTrails) {
+                // Trails count == fingers count that have ever been active.
+                final int trailsCount = mGesturePreviewTrails.size();
+                for (int index = 0; index < trailsCount; index++) {
+                    final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index);
+                    needsUpdatingGesturePreviewTrail |=
+                            trail.drawGestureTrail(canvas, mGesturePaint);
                 }
             }
+            if (needsUpdatingGesturePreviewTrail) {
+                mDrawingHandler.postUpdateGestureTrailPreview();
+            }
         }
         if (mDrawsGestureFloatingPreviewText) {
             drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText);
@@ -184,7 +222,7 @@
         canvas.translate(-mXOrigin, -mYOrigin);
     }
 
-    public void setGestureFloatingPreviewText(String gestureFloatingPreviewText) {
+    public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) {
         mGestureFloatingPreviewText = gestureFloatingPreviewText;
         invalidate();
     }
@@ -197,7 +235,8 @@
         mDrawingHandler.cancelAllMessages();
     }
 
-    private void drawGestureFloatingPreviewText(Canvas canvas, String gestureFloatingPreviewText) {
+    private void drawGestureFloatingPreviewText(final Canvas canvas,
+            final String gestureFloatingPreviewText) {
         if (TextUtils.isEmpty(gestureFloatingPreviewText)) {
             return;
         }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 4ada909..e1cb195 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -25,7 +25,10 @@
 import android.util.Log;
 
 import java.io.File;
-import java.io.RandomAccessFile;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
@@ -349,17 +352,21 @@
         // ad-hock ## HACK ##
         if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
 
+        FileInputStream inStream = null;
         try {
             // Read the version of the file
-            final RandomAccessFile raf = new RandomAccessFile(f, "r");
-            final int magic = raf.readInt();
+            inStream = new FileInputStream(f);
+            final ByteBuffer buffer = inStream.getChannel().map(
+                    FileChannel.MapMode.READ_ONLY, 0, f.length());
+            final int magic = buffer.getInt();
             if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) {
                 return false;
             }
-            final int formatVersion = raf.readInt();
-            final int headerSize = raf.readInt();
+            final int formatVersion = buffer.getInt();
+            final int headerSize = buffer.getInt();
             final HashMap<String, String> options = CollectionUtils.newHashMap();
-            BinaryDictInputOutput.populateOptionsFromFile(raf, headerSize, options);
+            BinaryDictInputOutput.populateOptions(buffer, headerSize, options);
+
             final String version = options.get(VERSION_KEY);
             if (null == version) {
                 // No version in the options : the format is unexpected
@@ -374,6 +381,14 @@
             return false;
         } catch (NumberFormatException e) {
             return false;
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index b23b7db..161b94c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -22,10 +22,13 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -307,33 +310,32 @@
         }
 
         /**
-         * Reads a string from a RandomAccessFile. This is the converse of the above method.
+         * Reads a string from a ByteBuffer. This is the converse of the above method.
          */
-        private static String readString(final RandomAccessFile source) throws IOException {
+        private static String readString(final ByteBuffer buffer) {
             final StringBuilder s = new StringBuilder();
-            int character = readChar(source);
+            int character = readChar(buffer);
             while (character != INVALID_CHARACTER) {
                 s.appendCodePoint(character);
-                character = readChar(source);
+                character = readChar(buffer);
             }
             return s.toString();
         }
 
         /**
-         * Reads a character from the file.
+         * Reads a character from the ByteBuffer.
          *
          * This follows the character format documented earlier in this source file.
          *
-         * @param source the file, positioned over an encoded character.
+         * @param buffer the buffer, positioned over an encoded character.
          * @return the character code.
          */
-        private static int readChar(RandomAccessFile source) throws IOException {
-            int character = source.readUnsignedByte();
+        private static int readChar(final ByteBuffer buffer) {
+            int character = readUnsignedByte(buffer);
             if (!fitsOnOneByte(character)) {
-                if (GROUP_CHARACTERS_TERMINATOR == character)
-                    return INVALID_CHARACTER;
+                if (GROUP_CHARACTERS_TERMINATOR == character) return INVALID_CHARACTER;
                 character <<= 16;
-                character += source.readUnsignedShort();
+                character += readUnsignedShort(buffer);
             }
             return character;
         }
@@ -1091,46 +1093,46 @@
     // readDictionaryBinary is the public entry point for them.
 
     static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
-    private static CharGroupInfo readCharGroup(RandomAccessFile source,
-            final int originalGroupAddress) throws IOException {
+    private static CharGroupInfo readCharGroup(final ByteBuffer buffer,
+            final int originalGroupAddress) {
         int addressPointer = originalGroupAddress;
-        final int flags = source.readUnsignedByte();
+        final int flags = readUnsignedByte(buffer);
         ++addressPointer;
         final int characters[];
         if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
             int index = 0;
-            int character = CharEncoding.readChar(source);
+            int character = CharEncoding.readChar(buffer);
             addressPointer += CharEncoding.getCharSize(character);
             while (-1 != character) {
                 characterBuffer[index++] = character;
-                character = CharEncoding.readChar(source);
+                character = CharEncoding.readChar(buffer);
                 addressPointer += CharEncoding.getCharSize(character);
             }
             characters = Arrays.copyOfRange(characterBuffer, 0, index);
         } else {
-            final int character = CharEncoding.readChar(source);
+            final int character = CharEncoding.readChar(buffer);
             addressPointer += CharEncoding.getCharSize(character);
             characters = new int[] { character };
         }
         final int frequency;
         if (0 != (FLAG_IS_TERMINAL & flags)) {
             ++addressPointer;
-            frequency = source.readUnsignedByte();
+            frequency = readUnsignedByte(buffer);
         } else {
             frequency = CharGroup.NOT_A_TERMINAL;
         }
         int childrenAddress = addressPointer;
         switch (flags & MASK_GROUP_ADDRESS_TYPE) {
         case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
-            childrenAddress += source.readUnsignedByte();
+            childrenAddress += readUnsignedByte(buffer);
             addressPointer += 1;
             break;
         case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
-            childrenAddress += source.readUnsignedShort();
+            childrenAddress += readUnsignedShort(buffer);
             addressPointer += 2;
             break;
         case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
-            childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort();
+            childrenAddress += readUnsignedInt24(buffer);
             addressPointer += 3;
             break;
         case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
@@ -1140,38 +1142,38 @@
         }
         ArrayList<WeightedString> shortcutTargets = null;
         if (0 != (flags & FLAG_HAS_SHORTCUT_TARGETS)) {
-            final long pointerBefore = source.getFilePointer();
+            final int pointerBefore = buffer.position();
             shortcutTargets = new ArrayList<WeightedString>();
-            source.readUnsignedShort(); // Skip the size
+            buffer.getShort(); // Skip the size
             while (true) {
-                final int targetFlags = source.readUnsignedByte();
-                final String word = CharEncoding.readString(source);
+                final int targetFlags = readUnsignedByte(buffer);
+                final String word = CharEncoding.readString(buffer);
                 shortcutTargets.add(new WeightedString(word,
                         targetFlags & FLAG_ATTRIBUTE_FREQUENCY));
                 if (0 == (targetFlags & FLAG_ATTRIBUTE_HAS_NEXT)) break;
             }
-            addressPointer += (source.getFilePointer() - pointerBefore);
+            addressPointer += buffer.position() - pointerBefore;
         }
         ArrayList<PendingAttribute> bigrams = null;
         if (0 != (flags & FLAG_HAS_BIGRAMS)) {
             bigrams = new ArrayList<PendingAttribute>();
             while (true) {
-                final int bigramFlags = source.readUnsignedByte();
+                final int bigramFlags = readUnsignedByte(buffer);
                 ++addressPointer;
                 final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
                 int bigramAddress = addressPointer;
                 switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
                 case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
-                    bigramAddress += sign * source.readUnsignedByte();
+                    bigramAddress += sign * readUnsignedByte(buffer);
                     addressPointer += 1;
                     break;
                 case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
-                    bigramAddress += sign * source.readUnsignedShort();
+                    bigramAddress += sign * readUnsignedShort(buffer);
                     addressPointer += 2;
                     break;
                 case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
-                    final int offset = ((source.readUnsignedByte() << 16)
-                            + source.readUnsignedShort());
+                    final int offset = (readUnsignedByte(buffer) << 16)
+                            + readUnsignedShort(buffer);
                     bigramAddress += sign * offset;
                     addressPointer += 3;
                     break;
@@ -1188,15 +1190,15 @@
     }
 
     /**
-     * Reads and returns the char group count out of a file and forwards the pointer.
+     * Reads and returns the char group count out of a buffer and forwards the pointer.
      */
-    private static int readCharGroupCount(RandomAccessFile source) throws IOException {
-        final int msb = source.readUnsignedByte();
+    private static int readCharGroupCount(final ByteBuffer buffer) {
+        final int msb = readUnsignedByte(buffer);
         if (MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT >= msb) {
             return msb;
         } else {
             return ((MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT & msb) << 8)
-                    + source.readUnsignedByte();
+                    + readUnsignedByte(buffer);
         }
     }
 
@@ -1204,31 +1206,29 @@
     // of this method. Since it performs direct, unbuffered random access to the file and
     // may be called hundreds of thousands of times, the resulting performance is not
     // reasonable without some kind of cache. Thus:
-    // TODO: perform buffered I/O here and in other places in the code.
     private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>();
     /**
      * Finds, as a string, the word at the address passed as an argument.
      *
-     * @param source the file to read from.
+     * @param buffer the buffer to read from.
      * @param headerSize the size of the header.
      * @param address the address to seek.
      * @return the word, as a string.
-     * @throws IOException if the file can't be read.
      */
-    private static String getWordAtAddress(final RandomAccessFile source, final long headerSize,
-            int address) throws IOException {
+    private static String getWordAtAddress(final ByteBuffer buffer, final int headerSize,
+            final int address) {
         final String cachedString = wordCache.get(address);
         if (null != cachedString) return cachedString;
-        final long originalPointer = source.getFilePointer();
-        source.seek(headerSize);
-        final int count = readCharGroupCount(source);
+        final int originalPointer = buffer.position();
+        buffer.position(headerSize);
+        final int count = readCharGroupCount(buffer);
         int groupOffset = getGroupCountSize(count);
         final StringBuilder builder = new StringBuilder();
         String result = null;
 
         CharGroupInfo last = null;
         for (int i = count - 1; i >= 0; --i) {
-            CharGroupInfo info = readCharGroup(source, groupOffset);
+            CharGroupInfo info = readCharGroup(buffer, groupOffset);
             groupOffset = info.mEndAddress;
             if (info.mOriginalAddress == address) {
                 builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
@@ -1239,9 +1239,9 @@
                 if (info.mChildrenAddress > address) {
                     if (null == last) continue;
                     builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
-                    source.seek(last.mChildrenAddress + headerSize);
+                    buffer.position(last.mChildrenAddress + headerSize);
                     groupOffset = last.mChildrenAddress + 1;
-                    i = source.readUnsignedByte();
+                    i = readUnsignedByte(buffer);
                     last = null;
                     continue;
                 }
@@ -1249,14 +1249,14 @@
             }
             if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
                 builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
-                source.seek(last.mChildrenAddress + headerSize);
+                buffer.position(last.mChildrenAddress + headerSize);
                 groupOffset = last.mChildrenAddress + 1;
-                i = source.readUnsignedByte();
+                i = readUnsignedByte(buffer);
                 last = null;
                 continue;
             }
         }
-        source.seek(originalPointer);
+        buffer.position(originalPointer);
         wordCache.put(address, result);
         return result;
     }
@@ -1269,44 +1269,47 @@
      * This will recursively read other nodes into the structure, populating the reverse
      * maps on the fly and using them to keep track of already read nodes.
      *
-     * @param source the data file, correctly positioned at the start of a node.
+     * @param buffer the buffer, correctly positioned at the start of a node.
      * @param headerSize the size, in bytes, of the file header.
      * @param reverseNodeMap a mapping from addresses to already read nodes.
      * @param reverseGroupMap a mapping from addresses to already read character groups.
      * @return the read node with all his children already read.
      */
-    private static Node readNode(RandomAccessFile source, long headerSize,
-            Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
+    private static Node readNode(final ByteBuffer buffer, final int headerSize,
+            final Map<Integer, Node> reverseNodeMap, final Map<Integer, CharGroup> reverseGroupMap)
             throws IOException {
-        final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
-        final int count = readCharGroupCount(source);
+        final int nodeOrigin = buffer.position() - headerSize;
+        final int count = readCharGroupCount(buffer);
         final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
         int groupOffset = nodeOrigin + getGroupCountSize(count);
         for (int i = count; i > 0; --i) {
-            CharGroupInfo info = readCharGroup(source, groupOffset);
+            CharGroupInfo info =readCharGroup(buffer, groupOffset);
             ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets;
             ArrayList<WeightedString> bigrams = null;
             if (null != info.mBigrams) {
                 bigrams = new ArrayList<WeightedString>();
                 for (PendingAttribute bigram : info.mBigrams) {
-                    final String word = getWordAtAddress(source, headerSize, bigram.mAddress);
+                    final String word = getWordAtAddress(
+                            buffer, headerSize, bigram.mAddress);
                     bigrams.add(new WeightedString(word, bigram.mFrequency));
                 }
             }
             if (hasChildrenAddress(info.mChildrenAddress)) {
                 Node children = reverseNodeMap.get(info.mChildrenAddress);
                 if (null == children) {
-                    final long currentPosition = source.getFilePointer();
-                    source.seek(info.mChildrenAddress + headerSize);
-                    children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap);
-                    source.seek(currentPosition);
+                    final int currentPosition = buffer.position();
+                    buffer.position(info.mChildrenAddress + headerSize);
+                    children = readNode(
+                            buffer, headerSize, reverseNodeMap, reverseGroupMap);
+                    buffer.position(currentPosition);
                 }
                 nodeContents.add(
-                        new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency,
-                                children));
+                        new CharGroup(info.mCharacters, shortcutTargets,
+                                bigrams, info.mFrequency, children));
             } else {
                 nodeContents.add(
-                        new CharGroup(info.mCharacters, shortcutTargets, bigrams, info.mFrequency));
+                        new CharGroup(info.mCharacters, shortcutTargets,
+                                bigrams, info.mFrequency));
             }
             groupOffset = info.mEndAddress;
         }
@@ -1318,12 +1321,13 @@
 
     /**
      * Helper function to get the binary format version from the header.
+     * @throws IOException
      */
-    private static int getFormatVersion(final RandomAccessFile source) throws IOException {
-        final int magic_v1 = source.readUnsignedShort();
-        if (VERSION_1_MAGIC_NUMBER == magic_v1) return source.readUnsignedByte();
-        final int magic_v2 = (magic_v1 << 16) + source.readUnsignedShort();
-        if (VERSION_2_MAGIC_NUMBER == magic_v2) return source.readUnsignedShort();
+    private static int getFormatVersion(final ByteBuffer buffer) throws IOException {
+        final int magic_v1 = readUnsignedShort(buffer);
+        if (VERSION_1_MAGIC_NUMBER == magic_v1) return readUnsignedByte(buffer);
+        final int magic_v2 = (magic_v1 << 16) + readUnsignedShort(buffer);
+        if (VERSION_2_MAGIC_NUMBER == magic_v2) return readUnsignedShort(buffer);
         return NOT_A_VERSION_NUMBER;
     }
 
@@ -1333,53 +1337,60 @@
      * The file is read at the current file pointer, so the caller must take care the pointer
      * is in the right place before calling this.
      */
-    public static void populateOptionsFromFile(final RandomAccessFile source, final long headerSize,
-            final HashMap<String, String> options) throws IOException {
-        while (source.getFilePointer() < headerSize) {
-            final String key = CharEncoding.readString(source);
-            final String value = CharEncoding.readString(source);
+    public static void populateOptions(final ByteBuffer buffer, final int headerSize,
+            final HashMap<String, String> options) {
+        while (buffer.position() < headerSize) {
+            final String key = CharEncoding.readString(buffer);
+            final String value = CharEncoding.readString(buffer);
             options.put(key, value);
         }
     }
 
     /**
-     * Reads a random access file and returns the memory representation of the dictionary.
+     * Reads a byte buffer and returns the memory representation of the dictionary.
      *
      * This high-level method takes a binary file and reads its contents, populating a
      * FusionDictionary structure. The optional dict argument is an existing dictionary to
      * which words from the file should be added. If it is null, a new dictionary is created.
      *
-     * @param source the file to read.
+     * @param buffer the buffer to read.
      * @param dict an optional dictionary to add words to, or null.
      * @return the created (or merged) dictionary.
      */
-    public static FusionDictionary readDictionaryBinary(final RandomAccessFile source,
+    public static FusionDictionary readDictionaryBinary(final ByteBuffer buffer,
             final FusionDictionary dict) throws IOException, UnsupportedFormatException {
         // Check file version
-        final int version = getFormatVersion(source);
-        if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION ) {
+        final int version = getFormatVersion(buffer);
+        if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) {
             throw new UnsupportedFormatException("This file has version " + version
                     + ", but this implementation does not support versions above "
                     + MAXIMUM_SUPPORTED_VERSION);
         }
 
-        // Read options
-        final int optionsFlags = source.readUnsignedShort();
+        // clear cache
+        wordCache.clear();
 
-        final long headerSize;
+        // Read options
+        final int optionsFlags = readUnsignedShort(buffer);
+
+        final int headerSize;
         final HashMap<String, String> options = new HashMap<String, String>();
         if (version < FIRST_VERSION_WITH_HEADER_SIZE) {
-            headerSize = source.getFilePointer();
+            headerSize = buffer.position();
         } else {
-            headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
-                    + (source.readUnsignedByte() << 8) + source.readUnsignedByte();
-            populateOptionsFromFile(source, headerSize, options);
-            source.seek(headerSize);
+            headerSize = buffer.getInt();
+            populateOptions(buffer, headerSize, options);
+            buffer.position(headerSize);
+        }
+
+        if (headerSize < 0) {
+            throw new UnsupportedFormatException("header size can't be negative.");
         }
 
         Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
         Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
-        final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping);
+        final Node root = readNode(
+                buffer, headerSize, reverseNodeMapping, reverseGroupMapping);
 
         FusionDictionary newDict = new FusionDictionary(root,
                 new FusionDictionary.DictionaryOptions(options,
@@ -1403,6 +1414,28 @@
     }
 
     /**
+     * Helper function to read one byte from ByteBuffer.
+     */
+    private static int readUnsignedByte(final ByteBuffer buffer) {
+        return ((int)buffer.get()) & 0xFF;
+    }
+
+    /**
+     * Helper function to read two byte from ByteBuffer.
+     */
+    private static int readUnsignedShort(final ByteBuffer buffer) {
+        return ((int)buffer.getShort()) & 0xFFFF;
+    }
+
+    /**
+     * Helper function to read three byte from ByteBuffer.
+     */
+    private static int readUnsignedInt24(final ByteBuffer buffer) {
+        final int value = readUnsignedByte(buffer) << 16;
+        return value + readUnsignedShort(buffer);
+    }
+
+    /**
      * Basic test to find out whether the file is a binary dictionary or not.
      *
      * Concretely this only tests the magic number.
@@ -1411,14 +1444,26 @@
      * @return true if it's a binary dictionary, false otherwise
      */
     public static boolean isBinaryDictionary(final String filename) {
+        FileInputStream inStream = null;
         try {
-            RandomAccessFile f = new RandomAccessFile(filename, "r");
-            final int version = getFormatVersion(f);
+            final File file = new File(filename);
+            inStream = new FileInputStream(file);
+            final ByteBuffer buffer = inStream.getChannel().map(
+                    FileChannel.MapMode.READ_ONLY, 0, file.length());
+            final int version = getFormatVersion(buffer);
             return (version >= MINIMUM_SUPPORTED_VERSION && version <= MAXIMUM_SUPPORTED_VERSION);
         } catch (FileNotFoundException e) {
             return false;
         } catch (IOException e) {
             return false;
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
         }
     }
 
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 6a0a7c2..a20958a 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -172,10 +172,10 @@
     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]));
+    memset(outputChars, 0, sizeof(outputChars));
+    memset(scores, 0, sizeof(scores));
+    memset(spaceIndices, 0, sizeof(spaceIndices));
+    memset(outputTypes, 0, sizeof(outputTypes));
 
     int count;
     if (isGesture || arraySize > 0) {
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 71f8a4f..9ad65b0 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -61,19 +61,19 @@
 }
 
 inline static void calcEditDistanceOneStep(int *editDistanceTable, const unsigned short *input,
-        const int inputLength, const unsigned short *output, const int outputLength) {
+        const int inputSize, const unsigned short *output, const int outputLength) {
     // TODO: Make sure that editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL] is not touched.
-    // Let dp[i][j] be editDistanceTable[i * (inputLength + 1) + j].
-    // Assuming that dp[0][0] ... dp[outputLength - 1][inputLength] are already calculated,
-    // and calculate dp[ouputLength][0] ... dp[outputLength][inputLength].
-    int *const current = editDistanceTable + outputLength * (inputLength + 1);
-    const int *const prev = editDistanceTable + (outputLength - 1) * (inputLength + 1);
+    // Let dp[i][j] be editDistanceTable[i * (inputSize + 1) + j].
+    // Assuming that dp[0][0] ... dp[outputLength - 1][inputSize] are already calculated,
+    // and calculate dp[ouputLength][0] ... dp[outputLength][inputSize].
+    int *const current = editDistanceTable + outputLength * (inputSize + 1);
+    const int *const prev = editDistanceTable + (outputLength - 1) * (inputSize + 1);
     const int *const prevprev =
-            outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputLength + 1) : 0;
+            outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputSize + 1) : 0;
     current[0] = outputLength;
     const uint32_t co = toBaseLowerCase(output[outputLength - 1]);
     const uint32_t prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0;
-    for (int i = 1; i <= inputLength; ++i) {
+    for (int i = 1; i <= inputSize; ++i) {
         const uint32_t ci = toBaseLowerCase(input[i - 1]);
         const uint16_t cost = (ci == co) ? 0 : 1;
         current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost));
@@ -84,11 +84,11 @@
 }
 
 inline static int getCurrentEditDistance(int *editDistanceTable, const int editDistanceTableWidth,
-        const int outputLength, const int inputLength) {
+        const int outputLength, const int inputSize) {
     if (DEBUG_EDIT_DISTANCE) {
-        AKLOGI("getCurrentEditDistance %d, %d", inputLength, outputLength);
+        AKLOGI("getCurrentEditDistance %d, %d", inputSize, outputLength);
     }
-    return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputLength];
+    return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputSize];
 }
 
 //////////////////////
@@ -109,12 +109,12 @@
     mTotalTraverseCount = 0;
 }
 
-void Correction::initCorrection(const ProximityInfo *pi, const int inputLength,
+void Correction::initCorrection(const ProximityInfo *pi, const int inputSize,
         const int maxDepth) {
     mProximityInfo = pi;
-    mInputLength = inputLength;
+    mInputSize = inputSize;
     mMaxDepth = maxDepth;
-    mMaxEditDistance = mInputLength < 5 ? 2 : mInputLength / 2;
+    mMaxEditDistance = mInputSize < 5 ? 2 : mInputSize / 2;
     // TODO: This is not supposed to be required.  Check what's going wrong with
     // editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL]
     initEditDistance(mEditDistanceTable);
@@ -168,22 +168,22 @@
 }
 
 int Correction::getFinalProbability(const int probability, unsigned short **word, int *wordLength) {
-    return getFinalProbabilityInternal(probability, word, wordLength, mInputLength);
+    return getFinalProbabilityInternal(probability, word, wordLength, mInputSize);
 }
 
 int Correction::getFinalProbabilityForSubQueue(const int probability, unsigned short **word,
-        int *wordLength, const int inputLength) {
-    return getFinalProbabilityInternal(probability, word, wordLength, inputLength);
+        int *wordLength, const int inputSize) {
+    return getFinalProbabilityInternal(probability, word, wordLength, inputSize);
 }
 
 int Correction::getFinalProbabilityInternal(const int probability, unsigned short **word,
-        int *wordLength, const int inputLength) {
+        int *wordLength, const int inputSize) {
     const int outputIndex = mTerminalOutputIndex;
     const int inputIndex = mTerminalInputIndex;
     *wordLength = outputIndex + 1;
     *word = mWord;
     int finalProbability= Correction::RankingAlgorithm::calculateFinalProbability(
-            inputIndex, outputIndex, probability, mEditDistanceTable, this, inputLength);
+            inputIndex, outputIndex, probability, mEditDistanceTable, this, inputSize);
     return finalProbability;
 }
 
@@ -270,13 +270,13 @@
     // TODO: use edit distance here
     return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance
             // Allow one char longer word for missing character
-            || (!mDoAutoCompletion && (mOutputIndex > mInputLength));
+            || (!mDoAutoCompletion && (mOutputIndex > mInputSize));
 }
 
 void Correction::addCharToCurrentWord(const int32_t c) {
     mWord[mOutputIndex] = c;
     const unsigned short *primaryInputWord = mProximityInfoState.getPrimaryInputWord();
-    calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputLength,
+    calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputSize,
             mWord, mOutputIndex + 1);
 }
 
@@ -325,7 +325,7 @@
     // Skip checking this node
     if (mNeedsToTraverseAllNodes || isSingleQuote(c)) {
         bool incremented = false;
-        if (mLastCharExceeded && mInputIndex == mInputLength - 1) {
+        if (mLastCharExceeded && mInputIndex == mInputSize - 1) {
             // TODO: Do not check the proximity if EditDistance exceeds the threshold
             const ProximityType matchId = mProximityInfoState.getMatchedProximityId(
                     mInputIndex, c, true, &proximityIndex);
@@ -354,7 +354,7 @@
         if (mExcessiveCount == 0 && mExcessivePos < mOutputIndex) {
             mExcessivePos = mOutputIndex;
         }
-        if (mExcessivePos < mInputLength - 1) {
+        if (mExcessivePos < mInputSize - 1) {
             mExceeding = mExcessivePos == mInputIndex && canTryCorrection;
         }
     }
@@ -373,7 +373,7 @@
         if (mTransposedCount == 0 && mTransposedPos < mOutputIndex) {
             mTransposedPos = mOutputIndex;
         }
-        if (mTransposedPos < mInputLength - 1) {
+        if (mTransposedPos < mInputSize - 1) {
             mTransposing = mInputIndex == mTransposedPos && canTryCorrection;
         }
     }
@@ -392,7 +392,7 @@
         } else {
             --mTransposedCount;
             if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
                     && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
                             || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 DUMP_WORD(mWord, mOutputIndex);
@@ -423,7 +423,7 @@
                 && isEquivalentChar(mProximityInfoState.getMatchedProximityId(
                         mInputIndex, mWord[mOutputIndex - 1], false))) {
             if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
                     && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
                             || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
@@ -453,7 +453,7 @@
         // As the current char turned out to be an unrelated char,
         // we will try other correction-types. Please note that mCorrectionStates[mOutputIndex]
         // here refers to the previous state.
-        if (mInputIndex < mInputLength - 1 && mOutputIndex > 0 && mTransposedCount > 0
+        if (mInputIndex < mInputSize - 1 && mOutputIndex > 0 && mTransposedCount > 0
                 && !mCorrectionStates[mOutputIndex].mTransposing
                 && mCorrectionStates[mOutputIndex - 1].mTransposing
                 && isEquivalentChar(mProximityInfoState.getMatchedProximityId(
@@ -490,7 +490,7 @@
             ++mSkippedCount;
             --mProximityCount;
             return processSkipChar(c, isTerminal, false);
-        } else if (mInputIndex - 1 < mInputLength
+        } else if (mInputIndex - 1 < mInputSize
                 && mSkippedCount > 0
                 && mCorrectionStates[mOutputIndex].mSkipping
                 && mCorrectionStates[mOutputIndex].mAdditionalProximityMatching
@@ -502,7 +502,7 @@
             mProximityMatching = true;
             ++mProximityCount;
             mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
-        } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputLength
+        } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputSize
                 && isEquivalentChar(
                         mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
             // 1.2. Excessive or transpose correction
@@ -513,7 +513,7 @@
                 incrementInputIndex();
             }
             if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
                     && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
                             || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 DUMP_WORD(mWord, mOutputIndex);
@@ -529,7 +529,7 @@
             // 3. Skip correction
             ++mSkippedCount;
             if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
                     && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
                             || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -542,7 +542,7 @@
             ++mProximityCount;
             mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
             if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
                     && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
                             || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -550,7 +550,7 @@
             }
         } else {
             if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
                     && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
                             || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
                 DUMP_WORD(mWord, mOutputIndex);
@@ -560,7 +560,7 @@
             return processUnrelatedCorrectionType();
         }
     } else if (secondTransposing) {
-        // If inputIndex is greater than mInputLength, that means there is no
+        // If inputIndex is greater than mInputSize, that means there is no
         // proximity chars. So, we don't need to check proximity.
         mMatching = true;
     } else if (isEquivalentChar(matchedProximityCharId)) {
@@ -573,7 +573,7 @@
         mDistances[mOutputIndex] =
                 mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, proximityIndex);
         if (DEBUG_CORRECTION
-                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
                 && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
                         || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
             AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -585,8 +585,8 @@
 
     // 4. Last char excessive correction
     mLastCharExceeded = mExcessiveCount == 0 && mSkippedCount == 0 && mTransposedCount == 0
-            && mProximityCount == 0 && (mInputIndex == mInputLength - 2);
-    const bool isSameAsUserTypedLength = (mInputLength == mInputIndex + 1) || mLastCharExceeded;
+            && mProximityCount == 0 && (mInputIndex == mInputSize - 2);
+    const bool isSameAsUserTypedLength = (mInputSize == mInputIndex + 1) || mLastCharExceeded;
     if (mLastCharExceeded) {
         ++mExcessiveCount;
     }
@@ -597,7 +597,7 @@
     }
 
     const bool needsToTryOnTerminalForTheLastPossibleExcessiveChar =
-            mExceeding && mInputIndex == mInputLength - 2;
+            mExceeding && mInputIndex == mInputSize - 2;
 
     // Finally, we are ready to go to the next character, the next "virtual node".
     // We should advance the input index.
@@ -613,7 +613,7 @@
         mTerminalInputIndex = mInputIndex - 1;
         mTerminalOutputIndex = mOutputIndex - 1;
         if (DEBUG_CORRECTION
-                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputLength)
+                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
                 && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
             DUMP_WORD(mWord, mOutputIndex);
             AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
@@ -627,9 +627,6 @@
     }
 }
 
-Correction::~Correction() {
-}
-
 inline static int getQuoteCount(const unsigned short *word, const int length) {
     int quoteCount = 0;
     for (int i = 0; i < length; ++i) {
@@ -651,7 +648,7 @@
 /* static */
 int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex,
         const int outputIndex, const int freq, int *editDistanceTable, const Correction *correction,
-        const int inputLength) {
+        const int inputSize) {
     const int excessivePos = correction->getExcessivePos();
     const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
     const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER;
@@ -663,55 +660,55 @@
     const bool lastCharExceeded = correction->mLastCharExceeded;
     const bool useFullEditDistance = correction->mUseFullEditDistance;
     const int outputLength = outputIndex + 1;
-    if (skippedCount >= inputLength || inputLength == 0) {
+    if (skippedCount >= inputSize || inputSize == 0) {
         return -1;
     }
 
     // TODO: find more robust way
-    bool sameLength = lastCharExceeded ? (inputLength == inputIndex + 2)
-            : (inputLength == inputIndex + 1);
+    bool sameLength = lastCharExceeded ? (inputSize == inputIndex + 2)
+            : (inputSize == inputIndex + 1);
 
     // TODO: use mExcessiveCount
-    const int matchCount = inputLength - correction->mProximityCount - excessiveCount;
+    const int matchCount = inputSize - correction->mProximityCount - excessiveCount;
 
     const unsigned short *word = correction->mWord;
     const bool skipped = skippedCount > 0;
 
     const int quoteDiffCount = max(0, getQuoteCount(word, outputLength)
-            - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputLength));
+            - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputSize));
 
     // TODO: Calculate edit distance for transposed and excessive
     int ed = 0;
     if (DEBUG_DICT_FULL) {
-        dumpEditDistance10ForDebug(editDistanceTable, correction->mInputLength, outputLength);
+        dumpEditDistance10ForDebug(editDistanceTable, correction->mInputSize, outputLength);
     }
     int adjustedProximityMatchedCount = proximityMatchedCount;
 
     int finalFreq = freq;
 
     if (DEBUG_CORRECTION_FREQ
-            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
+            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) {
         AKLOGI("FinalFreq0: %d", finalFreq);
     }
     // TODO: Optimize this.
     if (transposedCount > 0 || proximityMatchedCount > 0 || skipped || excessiveCount > 0) {
-        ed = getCurrentEditDistance(editDistanceTable, correction->mInputLength, outputLength,
-                inputLength) - transposedCount;
+        ed = getCurrentEditDistance(editDistanceTable, correction->mInputSize, outputLength,
+                inputSize) - transposedCount;
 
         const int matchWeight = powerIntCapped(typedLetterMultiplier,
-                max(inputLength, outputLength) - ed);
+                max(inputSize, outputLength) - ed);
         multiplyIntCapped(matchWeight, &finalFreq);
 
         // TODO: Demote further if there are two or more excessive chars with longer user input?
-        if (inputLength > outputLength) {
+        if (inputSize > outputLength) {
             multiplyRate(INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE, &finalFreq);
         }
 
         ed = max(0, ed - quoteDiffCount);
-        adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputLength)),
+        adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputSize)),
                 proximityMatchedCount);
         if (transposedCount <= 0) {
-            if (ed == 1 && (inputLength == outputLength - 1 || inputLength == outputLength + 1)) {
+            if (ed == 1 && (inputSize == outputLength - 1 || inputSize == outputLength + 1)) {
                 // Promote a word with just one skipped or excessive char
                 if (sameLength) {
                     multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE
@@ -740,8 +737,8 @@
     // Demotion for a word with missing character
     if (skipped) {
         const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE
-                * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
-                / (10 * inputLength
+                * (10 * inputSize - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
+                / (10 * inputSize
                         - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
         if (DEBUG_DICT_FULL) {
             AKLOGI("Demotion rate for missing character is %d.", demotionRate);
@@ -843,7 +840,7 @@
             ? adjustedProximityMatchedCount
             : (proximityMatchedCount + transposedCount);
     multiplyRate(
-            100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputLength, &finalFreq);
+            100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputSize, &finalFreq);
 
     // Promotion for an exactly matched word
     if (ed == 0) {
@@ -878,7 +875,7 @@
          e ... exceeding
          p ... proximity matching
      */
-    if (matchCount == inputLength && matchCount >= 2 && !skipped
+    if (matchCount == inputSize && matchCount >= 2 && !skipped
             && word[matchCount] == word[matchCount - 1]) {
         multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq);
     }
@@ -888,8 +885,8 @@
         multiplyIntCapped(fullWordMultiplier, &finalFreq);
     }
 
-    if (useFullEditDistance && outputLength > inputLength + 1) {
-        const int diff = outputLength - inputLength - 1;
+    if (useFullEditDistance && outputLength > inputSize + 1) {
+        const int diff = outputLength - inputSize - 1;
         const int divider = diff < 31 ? 1 << diff : S_INT_MAX;
         finalFreq = divider > finalFreq ? 1 : finalFreq / divider;
     }
@@ -899,8 +896,8 @@
     }
 
     if (DEBUG_CORRECTION_FREQ
-            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputLength)) {
-        DUMP_WORD(correction->getPrimaryInputWord(), inputLength);
+            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) {
+        DUMP_WORD(correction->getPrimaryInputWord(), inputSize);
         DUMP_WORD(correction->mWord, outputLength);
         AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d, A%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
                 skippedCount, transposedCount, excessiveCount, additionalProximityCount,
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index 9899f6e..f016d54 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -18,6 +18,7 @@
 #define LATINIME_CORRECTION_H
 
 #include <cassert>
+#include <cstring> // for memset()
 #include <stdint.h>
 
 #include "correction_state.h"
@@ -38,11 +39,27 @@
         NOT_ON_TERMINAL
     } CorrectionType;
 
-    Correction() {};
-    virtual ~Correction();
+    Correction()
+            : mProximityInfo(0), mUseFullEditDistance(false), mDoAutoCompletion(false),
+              mMaxEditDistance(0), mMaxDepth(0), mInputSize(0), mSpaceProximityPos(0),
+              mMissingSpacePos(0), mTerminalInputIndex(0), mTerminalOutputIndex(0), mMaxErrors(0),
+              mTotalTraverseCount(0), mNeedsToTraverseAllNodes(false), mOutputIndex(0),
+              mInputIndex(0), mEquivalentCharCount(0), mProximityCount(0), mExcessiveCount(0),
+              mTransposedCount(0), mSkippedCount(0), mTransposedPos(0), mExcessivePos(0),
+              mSkipPos(0), mLastCharExceeded(false), mMatching(false), mProximityMatching(false),
+              mAdditionalProximityMatching(false), mExceeding(false), mTransposing(false),
+              mSkipping(false), mProximityInfoState() {
+        memset(mWord, 0, sizeof(mWord));
+        memset(mDistances, 0, sizeof(mDistances));
+        memset(mEditDistanceTable, 0, sizeof(mEditDistanceTable));
+        // NOTE: mCorrectionStates is an array of instances.
+        // No need to initialize it explicitly here.
+    }
+
+    virtual ~Correction() {}
     void resetCorrection();
     void initCorrection(
-            const ProximityInfo *pi, const int inputLength, const int maxWordLength);
+            const ProximityInfo *pi, const int inputSize, const int maxWordLength);
     void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
 
     // TODO: remove
@@ -66,7 +83,7 @@
             const bool isSpaceProximity, const unsigned short *word);
     int getFinalProbability(const int probability, unsigned short **word, int *wordLength);
     int getFinalProbabilityForSubQueue(const int probability, unsigned short **word,
-            int *wordLength, const int inputLength);
+            int *wordLength, const int inputSize);
 
     CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal);
 
@@ -90,7 +107,7 @@
      public:
         static int calculateFinalProbability(const int inputIndex, const int depth,
                 const int probability, int *editDistanceTable, const Correction *correction,
-                const int inputLength);
+                const int inputSize);
         static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
                 const int wordCount, const Correction *correction, const bool isSpaceProximity,
                 const unsigned short *word);
@@ -105,9 +122,9 @@
 
     // proximity info state
     void initInputParams(const ProximityInfo *proximityInfo, const int32_t *inputCodes,
-            const int inputLength, const int *xCoordinates, const int *yCoordinates) {
-        mProximityInfoState.initInputParams(
-                proximityInfo, inputCodes, inputLength, xCoordinates, yCoordinates);
+            const int inputSize, const int *xCoordinates, const int *yCoordinates) {
+        mProximityInfoState.initInputParams(0, MAX_POINT_TO_KEY_LENGTH,
+                proximityInfo, inputCodes, inputSize, xCoordinates, yCoordinates, 0, 0, false);
     }
 
     const unsigned short *getPrimaryInputWord() const {
@@ -204,7 +221,7 @@
     inline CorrectionType processUnrelatedCorrectionType();
     inline void addCharToCurrentWord(const int32_t c);
     inline int getFinalProbabilityInternal(const int probability, unsigned short **word,
-            int *wordLength, const int inputLength);
+            int *wordLength, const int inputSize);
 
     static const int TYPED_LETTER_MULTIPLIER = 2;
     static const int FULL_WORD_MULTIPLIER = 2;
@@ -214,7 +231,7 @@
     bool mDoAutoCompletion;
     int mMaxEditDistance;
     int mMaxDepth;
-    int mInputLength;
+    int mInputSize;
     int mSpaceProximityPos;
     int mMissingSpacePos;
     int mTerminalInputIndex;
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 929b303..9b53007 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -294,6 +294,9 @@
 
 #define MAX_SPACES_INTERNAL 16
 
+// Max Distance between point to key
+#define MAX_POINT_TO_KEY_LENGTH 10000000
+
 // TODO: Reduce this constant if possible; check the maximum number of digraphs in the same
 // word in the dictionary for languages with digraphs, like German and French
 #define DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH 5
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index 8ce8c8b..2fbe83e 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -30,11 +30,15 @@
 
 // TODO: Change the type of all keyCodes to uint32_t
 Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust,
-        int typedLetterMultiplier, int fullWordMultiplier,
-        int maxWordLength, int maxWords, int maxPredictions)
-    : mDict(static_cast<unsigned char *>(dict)),
-      mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)),
-      mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust) {
+        int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords,
+        int maxPredictions)
+        : mDict(static_cast<unsigned char *>(dict)),
+          mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)),
+          mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust),
+          mUnigramDictionary(new UnigramDictionary(mOffsetDict, typedLetterMultiplier,
+                  fullWordMultiplier, maxWordLength, maxWords, BinaryFormat::getFlags(mDict))),
+          mBigramDictionary(new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions)),
+          mGestureDecoder(new GestureDecoderWrapper(maxWordLength, maxWords)) {
     if (DEBUG_DICT) {
         if (MAX_WORD_LENGTH_INTERNAL < maxWordLength) {
             AKLOGI("Max word length (%d) is greater than %d",
@@ -42,11 +46,6 @@
             AKLOGI("IN NATIVE SUGGEST Version: %d", (mDict[0] & 0xFF));
         }
     }
-    const unsigned int options = BinaryFormat::getFlags(mDict);
-    mUnigramDictionary = new UnigramDictionary(mOffsetDict, typedLetterMultiplier,
-            fullWordMultiplier, maxWordLength, maxWords, options);
-    mBigramDictionary = new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions);
-    mGestureDecoder = new GestureDecoderWrapper(maxWordLength, maxWords);
 }
 
 Dictionary::~Dictionary() {
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
index 146eb80..f30e9fc 100644
--- a/native/jni/src/geometry_utils.h
+++ b/native/jni/src/geometry_utils.h
@@ -19,7 +19,6 @@
 
 #include <cmath>
 
-#define MAX_DISTANCE 10000000
 #define MAX_PATHS 2
 
 #define DEBUG_DECODER false
diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.h b/native/jni/src/gesture/gesture_decoder_wrapper.h
index 2f6220b..92e1ded 100644
--- a/native/jni/src/gesture/gesture_decoder_wrapper.h
+++ b/native/jni/src/gesture/gesture_decoder_wrapper.h
@@ -29,8 +29,8 @@
 
 class GestureDecoderWrapper : public IncrementalDecoderInterface {
  public:
-    GestureDecoderWrapper(const int maxWordLength, const int maxWords) {
-        mIncrementalDecoderInterface = getGestureDecoderInstance(maxWordLength, maxWords);
+    GestureDecoderWrapper(const int maxWordLength, const int maxWords)
+            : mIncrementalDecoderInterface(getGestureDecoderInstance(maxWordLength, maxWords)) {
     }
 
     virtual ~GestureDecoderWrapper() {
diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.h b/native/jni/src/gesture/incremental_decoder_wrapper.h
index 1f4d8a0..da7afdb 100644
--- a/native/jni/src/gesture/incremental_decoder_wrapper.h
+++ b/native/jni/src/gesture/incremental_decoder_wrapper.h
@@ -29,8 +29,8 @@
 
 class IncrementalDecoderWrapper : public IncrementalDecoderInterface {
  public:
-    IncrementalDecoderWrapper(const int maxWordLength, const int maxWords) {
-        mIncrementalDecoderInterface = getIncrementalDecoderInstance(maxWordLength, maxWords);
+    IncrementalDecoderWrapper(const int maxWordLength, const int maxWords)
+            : mIncrementalDecoderInterface(getIncrementalDecoderInstance(maxWordLength, maxWords)) {
     }
 
     virtual ~IncrementalDecoderWrapper() {
diff --git a/native/jni/src/hash_map_compat.h b/native/jni/src/hash_map_compat.h
new file mode 100644
index 0000000..116359a
--- /dev/null
+++ b/native/jni/src/hash_map_compat.h
@@ -0,0 +1,34 @@
+/*
+ * 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_HASH_MAP_COMPAT_H
+#define LATINIME_HASH_MAP_COMPAT_H
+
+// TODO: Use std::unordered_map that has been standardized in C++11
+
+#ifdef __APPLE__
+#include <ext/hash_map>
+#else // __APPLE__
+#include <hash_map>
+#endif // __APPLE__
+
+#ifdef __SGI_STL_PORT
+#define hash_map_compat stlport::hash_map
+#else // __SGI_STL_PORT
+#define hash_map_compat __gnu_cxx::hash_map
+#endif // __SGI_STL_PORT
+
+#endif // LATINIME_HASH_MAP_COMPAT_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 1b9bac0..e681f6f 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -29,6 +29,9 @@
 
 namespace latinime {
 
+/* static */ const int ProximityInfo::NOT_A_CODE = -1;
+/* static */ const float ProximityInfo::NOT_A_DISTANCE_FLOAT = -1.0f;
+
 static inline void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray, jsize len,
         jint *buffer) {
     if (jArray && buffer) {
@@ -54,16 +57,17 @@
         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),
+        : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), 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) {
+                  && sweetSpotCenterYs && sweetSpotRadii),
+          mProximityCharsArray(new int32_t[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE
+                  /* proximityGridLength */]) {
     const int proximityGridLength = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
     if (DEBUG_PROXIMITY_INFO) {
         AKLOGI("Create proximity info array %d", proximityGridLength);
@@ -75,7 +79,6 @@
     }
     memset(mLocaleStr, 0, sizeof(mLocaleStr));
     env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
-    mProximityCharsArray = new int32_t[proximityGridLength];
     safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityGridLength, mProximityCharsArray);
     safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
     safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index 7ee633b..822909b 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -108,8 +108,8 @@
     static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64;
     // The upper limit of the char code in mCodeToKeyIndex
     static const int MAX_CHAR_CODE = 127;
-    static const float NOT_A_DISTANCE_FLOAT = -1.0f;
-    static const int NOT_A_CODE = -1;
+    static const int NOT_A_CODE;
+    static const float NOT_A_DISTANCE_FLOAT;
 
     int getStartIndexFromCoordinates(const int x, const int y) const;
     void initializeCodeToKeyIndex();
@@ -129,8 +129,6 @@
     }
 
     const int MAX_PROXIMITY_CHARS_SIZE;
-    const int KEYBOARD_WIDTH;
-    const int KEYBOARD_HEIGHT;
     const int GRID_WIDTH;
     const int GRID_HEIGHT;
     const int MOST_COMMON_KEY_WIDTH;
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 86c8a69..f01b81e 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -20,13 +20,15 @@
 #define LOG_TAG "LatinIME: proximity_info_state.cpp"
 
 #include "defines.h"
+#include "geometry_utils.h"
 #include "proximity_info.h"
 #include "proximity_info_state.h"
 
 namespace latinime {
-void ProximityInfoState::initInputParams(
-        const ProximityInfo *proximityInfo, const int32_t *inputCodes, const int inputLength,
-        const int *xCoordinates, const int *yCoordinates) {
+void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength,
+        const ProximityInfo *proximityInfo, const int32_t *const inputCodes, const int inputSize,
+        const int *const xCoordinates, const int *const yCoordinates, const int *const times,
+        const int *const pointerIds, const bool isGeometric) {
     mProximityInfo = proximityInfo;
     mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
     mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare();
@@ -36,76 +38,146 @@
     mCellWidth = proximityInfo->getCellWidth();
     mGridHeight = proximityInfo->getGridWidth();
     mGridWidth = proximityInfo->getGridHeight();
-    const int normalizedSquaredDistancesLength =
-            MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL;
-    for (int i = 0; i < normalizedSquaredDistancesLength; ++i) {
-        mNormalizedSquaredDistances[i] = NOT_A_DISTANCE;
-    }
 
-    memset(mInputCodes, 0,
-            MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE_INTERNAL * sizeof(mInputCodes[0]));
+    memset(mInputCodes, 0, sizeof(mInputCodes));
 
-    for (int i = 0; i < inputLength; ++i) {
-        const int32_t primaryKey = inputCodes[i];
-        const int x = xCoordinates[i];
-        const int y = yCoordinates[i];
-        int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
-        mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
-    }
-
-    if (DEBUG_PROXIMITY_CHARS) {
-        for (int i = 0; i < inputLength; ++i) {
-            AKLOGI("---");
-            for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
-                int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
-                int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
-                icc += 0;
-                icfjc += 0;
-                AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
-            }
+    if (!isGeometric && pointerId == 0) {
+        // Initialize
+        // - mInputCodes
+        // - mNormalizedSquaredDistances
+        // TODO: Merge
+        for (int i = 0; i < inputSize; ++i) {
+            const int32_t primaryKey = inputCodes[i];
+            const int x = xCoordinates[i];
+            const int y = yCoordinates[i];
+            int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL];
+            mProximityInfo->calculateNearbyKeyCodes(x, y, primaryKey, proximities);
         }
-    }
-    mInputXCoordinates = xCoordinates;
-    mInputYCoordinates = yCoordinates;
-    mTouchPositionCorrectionEnabled =
-            mHasTouchPositionCorrectionData && xCoordinates && yCoordinates;
-    mInputLength = inputLength;
-    for (int i = 0; i < inputLength; ++i) {
-        mPrimaryInputWord[i] = getPrimaryCharAt(i);
-    }
-    mPrimaryInputWord[inputLength] = 0;
-    if (DEBUG_PROXIMITY_CHARS) {
-        AKLOGI("--- initInputParams");
-    }
-    for (int i = 0; i < mInputLength; ++i) {
-        const int *proximityChars = getProximityCharsAt(i);
-        const int primaryKey = proximityChars[0];
-        const int x = xCoordinates[i];
-        const int y = yCoordinates[i];
+
         if (DEBUG_PROXIMITY_CHARS) {
-            int a = x + y + primaryKey;
-            a += 0;
-            AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
-        }
-        for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) {
-            const int currentChar = proximityChars[j];
-            const float squaredDistance =
-                    hasInputCoordinates() ? calculateNormalizedSquaredDistance(
-                            mProximityInfo->getKeyIndex(currentChar), i) :
-                            NOT_A_DISTANCE_FLOAT;
-            if (squaredDistance >= 0.0f) {
-                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
-                        (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
-            } else {
-                mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
-                        (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO :
-                                PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
-            }
-            if (DEBUG_PROXIMITY_CHARS) {
-                AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+            for (int i = 0; i < inputSize; ++i) {
+                AKLOGI("---");
+                for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL; ++j) {
+                    int icc = mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+                    int icfjc = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j];
+                    icc += 0;
+                    icfjc += 0;
+                    AKLOGI("--- (%d)%c,%c", i, icc, icfjc); AKLOGI("--- A<%d>,B<%d>", icc, icfjc);
+                }
             }
         }
     }
+
+    ///////////////////////
+    // Setup touch points
+    mMaxPointToKeyLength = maxPointToKeyLength;
+    mInputXs.clear();
+    mInputYs.clear();
+    mTimes.clear();
+    mLengthCache.clear();
+    mDistanceCache.clear();
+
+    mInputSize = 0;
+    if (xCoordinates && yCoordinates) {
+        const bool proximityOnly = !isGeometric && (xCoordinates[0] < 0 || yCoordinates[0] < 0);
+        for (int i = 0; i < inputSize; ++i) {
+            // Assuming pointerId == 0 if pointerIds is null.
+            const int pid = pointerIds ? pointerIds[i] : 0;
+            if (pointerId == pid) {
+                const int c = isGeometric ? NOT_A_COORDINATE : getPrimaryCharAt(i);
+                const int x = proximityOnly ? NOT_A_COORDINATE : xCoordinates[i];
+                const int y = proximityOnly ? NOT_A_COORDINATE : yCoordinates[i];
+                const int time = times ? times[i] : -1;
+                if (pushTouchPoint(c, x, y, time, isGeometric)) {
+                    ++mInputSize;
+                }
+            }
+        }
+    }
+
+    if (mInputSize > 0) {
+        const int keyCount = mProximityInfo->getKeyCount();
+        mDistanceCache.resize(mInputSize * keyCount);
+        for (int i = 0; i < mInputSize; ++i) {
+            for (int k = 0; k < keyCount; ++k) {
+                const int index = i * keyCount + k;
+                const int x = mInputXs[i];
+                const int y = mInputYs[i];
+                mDistanceCache[index] =
+                        mProximityInfo->getNormalizedSquaredDistanceFromCenterFloat(k, x, y);
+            }
+        }
+    }
+
+    // end
+    ///////////////////////
+
+    memset(mNormalizedSquaredDistances, NOT_A_DISTANCE, sizeof(mNormalizedSquaredDistances));
+    memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
+    mTouchPositionCorrectionEnabled = mInputSize > 0 && mHasTouchPositionCorrectionData
+            && xCoordinates && yCoordinates && !isGeometric;
+    if (!isGeometric && pointerId == 0) {
+        for (int i = 0; i < inputSize; ++i) {
+            mPrimaryInputWord[i] = getPrimaryCharAt(i);
+        }
+
+        for (int i = 0; i < mInputSize && mTouchPositionCorrectionEnabled; ++i) {
+            const int *proximityChars = getProximityCharsAt(i);
+            const int primaryKey = proximityChars[0];
+            const int x = xCoordinates[i];
+            const int y = yCoordinates[i];
+            if (DEBUG_PROXIMITY_CHARS) {
+                int a = x + y + primaryKey;
+                a += 0;
+                AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
+            }
+            for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) {
+                const int currentChar = proximityChars[j];
+                const float squaredDistance =
+                        hasInputCoordinates() ? calculateNormalizedSquaredDistance(
+                                mProximityInfo->getKeyIndex(currentChar), i) :
+                                NOT_A_DISTANCE_FLOAT;
+                if (squaredDistance >= 0.0f) {
+                    mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
+                            (int) (squaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
+                } else {
+                    mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] =
+                            (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO :
+                                    PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
+                }
+                if (DEBUG_PROXIMITY_CHARS) {
+                    AKLOGI("--- Proximity (%d) = %c", j, currentChar);
+                }
+            }
+        }
+    }
+}
+
+bool ProximityInfoState::pushTouchPoint(const int nodeChar, int x, int y,
+        const int time, const bool sample) {
+    const uint32_t size = mInputXs.size();
+    // TODO: Should have a const variable for 10
+    const int sampleRate = mProximityInfo->getMostCommonKeyWidth() / 10;
+    if (size > 0) {
+        const int dist = getDistanceInt(x, y, mInputXs[size - 1], mInputYs[size - 1]);
+        if (sample && dist < sampleRate) {
+            return false;
+        }
+        mLengthCache.push_back(mLengthCache[size - 1] + dist);
+    } else {
+        mLengthCache.push_back(0);
+    }
+    if (nodeChar >= 0 && (x < 0 || y < 0)) {
+        const int keyId = mProximityInfo->getKeyIndex(nodeChar);
+        if (keyId >= 0) {
+            x = mProximityInfo->getKeyCenterXOfIdG(keyId);
+            y = mProximityInfo->getKeyCenterYOfIdG(keyId);
+        }
+    }
+    mInputXs.push_back(x);
+    mInputYs.push_back(y);
+    mTimes.push_back(time);
+    return true;
 }
 
 float ProximityInfoState::calculateNormalizedSquaredDistance(
@@ -116,7 +188,7 @@
     if (!mProximityInfo->hasSweetSpotData(keyIndex)) {
         return NOT_A_DISTANCE_FLOAT;
     }
-    if (NOT_A_COORDINATE == mInputXCoordinates[inputIndex]) {
+    if (NOT_A_COORDINATE == mInputXs[inputIndex]) {
         return NOT_A_DISTANCE_FLOAT;
     }
     const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(
@@ -125,12 +197,37 @@
     return squaredDistance / squaredRadius;
 }
 
+int ProximityInfoState::getDuration(const int index) const {
+    if (mInputSize > 0 && index > 0 && index < static_cast<int>(mInputSize) - 1) {
+        return mTimes[index + 1] - mTimes[index - 1];
+    }
+    return 0;
+}
+
+float ProximityInfoState::getPointToKeyLength(int inputIndex, int charCode, float scale) {
+    const int keyId = mProximityInfo->getKeyIndex(charCode);
+    if (keyId >= 0) {
+        const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
+        return min(mDistanceCache[index] * scale, mMaxPointToKeyLength);
+    }
+    return 0;
+}
+
+int ProximityInfoState::getKeyKeyDistance(int key0, int key1) {
+    return mProximityInfo->getKeyKeyDistanceG(key0, key1);
+}
+
+int ProximityInfoState::getSpaceY() {
+    const int keyId = mProximityInfo->getKeyIndex(' ');
+    return mProximityInfo->getKeyCenterYOfIdG(keyId);
+}
+
 float ProximityInfoState::calculateSquaredDistanceFromSweetSpotCenter(
         const int keyIndex, const int inputIndex) const {
     const float sweetSpotCenterX = mProximityInfo->getSweetSpotCenterXAt(keyIndex);
     const float sweetSpotCenterY = mProximityInfo->getSweetSpotCenterYAt(keyIndex);
-    const float inputX = static_cast<float>(mInputXCoordinates[inputIndex]);
-    const float inputY = static_cast<float>(mInputYCoordinates[inputIndex]);
+    const float inputX = static_cast<float>(mInputXs[inputIndex]);
+    const float inputY = static_cast<float>(mInputYs[inputIndex]);
     return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY);
 }
 } // namespace latinime
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index 2fb0ae0..26fd89b 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -17,8 +17,10 @@
 #ifndef LATINIME_PROXIMITY_INFO_STATE_H
 #define LATINIME_PROXIMITY_INFO_STATE_H
 
+#include <cstring> // for memset()
 #include <stdint.h>
 #include <string>
+#include <vector>
 
 #include "char_utils.h"
 #include "defines.h"
@@ -40,14 +42,26 @@
     /////////////////////////////////////////
     // Defined in proximity_info_state.cpp //
     /////////////////////////////////////////
-    void initInputParams(
-            const ProximityInfo *proximityInfo, const int32_t *inputCodes, const int inputLength,
-            const int *xCoordinates, const int *yCoordinates);
+    void initInputParams(const int pointerId, const float maxPointToKeyLength,
+            const ProximityInfo *proximityInfo, const int32_t *const inputCodes,
+            const int inputSize, const int *xCoordinates, const int *yCoordinates,
+            const int *const times, const int *const pointerIds, const bool isGeometric);
 
     /////////////////////////////////////////
     // Defined here                        //
     /////////////////////////////////////////
-    ProximityInfoState() {};
+    ProximityInfoState()
+            : mProximityInfo(0), mMaxPointToKeyLength(0),
+              mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(),
+              mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
+              mInputXs(), mInputYs(), mTimes(), mDistanceCache(), mLengthCache(),
+              mTouchPositionCorrectionEnabled(false), mInputSize(0) {
+        memset(mInputCodes, 0, sizeof(mInputCodes));
+        memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
+        memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
+    }
+
+    virtual ~ProximityInfoState() {}
 
     inline unsigned short getPrimaryCharAt(const int index) const {
         return getProximityCharsAt(index)[0];
@@ -65,14 +79,14 @@
     }
 
     inline bool existsAdjacentProximityChars(const int index) const {
-        if (index < 0 || index >= mInputLength) return false;
+        if (index < 0 || index >= mInputSize) return false;
         const int currentChar = getPrimaryCharAt(index);
         const int leftIndex = index - 1;
         if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) {
             return true;
         }
         const int rightIndex = index + 1;
-        if (rightIndex < mInputLength && existsCharInProximityAt(rightIndex, currentChar)) {
+        if (rightIndex < mInputSize && existsCharInProximityAt(rightIndex, currentChar)) {
             return true;
         }
         return false;
@@ -158,7 +172,7 @@
     }
 
     inline bool sameAsTyped(const unsigned short *word, int length) const {
-        if (length != mInputLength) {
+        if (length != mInputSize) {
             return false;
         }
         const int *inputCodes = mInputCodes;
@@ -172,6 +186,34 @@
         return true;
     }
 
+    int getDuration(const int index) const;
+
+    bool isUsed() const {
+        return mInputSize > 0;
+    }
+
+    uint32_t size() const {
+        return mInputSize;
+    }
+
+    int getInputX(int index) const {
+        return mInputXs[index];
+    }
+
+    int getInputY(int index) const {
+        return mInputYs[index];
+    }
+
+    int getLengthCache(int index) const {
+        return mLengthCache[index];
+    }
+
+    float getPointToKeyLength(int inputIndex, int charCode, float scale);
+
+    int getKeyKeyDistance(int key0, int key1);
+
+    int getSpaceY();
+
  private:
     DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
     /////////////////////////////////////////
@@ -182,13 +224,14 @@
     float calculateSquaredDistanceFromSweetSpotCenter(
             const int keyIndex, const int inputIndex) const;
 
+    bool pushTouchPoint(const int nodeChar, int x, int y, const int time, const bool sample);
     /////////////////////////////////////////
     // Defined here                        //
     /////////////////////////////////////////
     inline float square(const float x) const { return x * x; }
 
     bool hasInputCoordinates() const {
-        return mInputXCoordinates && mInputYCoordinates;
+        return mInputXs.size() > 0 && mInputYs.size() > 0;
     }
 
     inline const int *getProximityCharsAt(const int index) const {
@@ -197,6 +240,7 @@
 
     // const
     const ProximityInfo *mProximityInfo;
+    float mMaxPointToKeyLength;
     bool mHasTouchPositionCorrectionData;
     int mMostCommonKeyWidthSquare;
     std::string mLocaleStr;
@@ -206,12 +250,15 @@
     int mGridHeight;
     int mGridWidth;
 
-    const int *mInputXCoordinates;
-    const int *mInputYCoordinates;
+    std::vector<int> mInputXs;
+    std::vector<int> mInputYs;
+    std::vector<int> mTimes;
+    std::vector<float> mDistanceCache;
+    std::vector<int>  mLengthCache;
     bool mTouchPositionCorrectionEnabled;
     int32_t mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
     int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
-    int mInputLength;
+    int mInputSize;
     unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL];
 };
 } // namespace latinime
diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h
index 1ae9c7c..34ab8f0 100644
--- a/native/jni/src/terminal_attributes.h
+++ b/native/jni/src/terminal_attributes.h
@@ -30,13 +30,13 @@
  public:
     class ShortcutIterator {
         const uint8_t *const mDict;
-        bool mHasNextShortcutTarget;
         int mPos;
+        bool mHasNextShortcutTarget;
 
      public:
-        ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags) : mDict(dict),
-                mPos(pos) {
-            mHasNextShortcutTarget = (0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS));
+        ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags)
+                : mDict(dict), mPos(pos),
+                  mHasNextShortcutTarget(0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS)) {
         }
 
         inline bool hasNextShortcutTarget() const {
@@ -62,13 +62,6 @@
         }
     };
 
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
-    const uint8_t *const mDict;
-    const uint8_t mFlags;
-    const int mStartPos;
-
- public:
     TerminalAttributes(const uint8_t *const dict, const uint8_t flags, const int pos) :
             mDict(dict), mFlags(flags), mStartPos(pos) {
     }
@@ -78,6 +71,12 @@
         // skipped quickly, so we ignore it.
         return ShortcutIterator(mDict, mStartPos + BinaryFormat::SHORTCUT_LIST_SIZE_SIZE, mFlags);
     }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
+    const uint8_t *const mDict;
+    const uint8_t mFlags;
+    const int mStartPos;
 };
 } // namespace latinime
 #endif // LATINIME_TERMINAL_ATTRIBUTES_H
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index 705defc..ba3c2db 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -237,7 +237,7 @@
 
 void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
         const int *xcoordinates, const int *ycoordinates, const int *codes,
-        const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
+        const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
         const bool useFullEditDistance, Correction *correction,
         WordsPriorityQueuePool *queuePool) const {
 
@@ -247,7 +247,7 @@
 
     PROF_START(1);
     getOneWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, bigramMap, bigramFilter,
-            useFullEditDistance, inputLength, correction, queuePool);
+            useFullEditDistance, inputSize, correction, queuePool);
     PROF_END(1);
 
     PROF_START(2);
@@ -263,7 +263,7 @@
     WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
     if (masterQueue->size() > 0) {
         float nsForMaster = masterQueue->getHighestNormalizedScore(
-                correction->getPrimaryInputWord(), inputLength, 0, 0, 0);
+                correction->getPrimaryInputWord(), inputSize, 0, 0, 0);
         hasAutoCorrectionCandidate = (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD);
     }
     PROF_END(4);
@@ -271,9 +271,9 @@
     PROF_START(5);
     // Multiple word suggestions
     if (SUGGEST_MULTIPLE_WORDS
-            && inputLength >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) {
+            && inputSize >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) {
         getSplitMultipleWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
-                useFullEditDistance, inputLength, correction, queuePool,
+                useFullEditDistance, inputSize, correction, queuePool,
                 hasAutoCorrectionCandidate);
     }
     PROF_END(5);
@@ -304,15 +304,15 @@
 }
 
 void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
-        const int *yCoordinates, const int *codes, const int inputLength,
+        const int *yCoordinates, const int *codes, const int inputSize,
         Correction *correction) const {
     if (DEBUG_DICT) {
         AKLOGI("initSuggest");
-        DUMP_WORD_INT(codes, inputLength);
+        DUMP_WORD_INT(codes, inputSize);
     }
-    correction->initInputParams(proximityInfo, codes, inputLength, xCoordinates, yCoordinates);
-    const int maxDepth = min(inputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
-    correction->initCorrection(proximityInfo, inputLength, maxDepth);
+    correction->initInputParams(proximityInfo, codes, inputSize, xCoordinates, yCoordinates);
+    const int maxDepth = min(inputSize * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
+    correction->initCorrection(proximityInfo, inputSize, maxDepth);
 }
 
 static const char QUOTE = '\'';
@@ -321,15 +321,15 @@
 void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo,
         const int *xcoordinates, const int *ycoordinates, const int *codes,
         const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-        const bool useFullEditDistance, const int inputLength,
+        const bool useFullEditDistance, const int inputSize,
         Correction *correction, WordsPriorityQueuePool *queuePool) const {
-    initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputLength, correction);
-    getSuggestionCandidates(useFullEditDistance, inputLength, bigramMap, bigramFilter, correction,
+    initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputSize, correction);
+    getSuggestionCandidates(useFullEditDistance, inputSize, bigramMap, bigramFilter, correction,
             queuePool, true /* doAutoCompletion */, DEFAULT_MAX_ERRORS, FIRST_WORD_INDEX);
 }
 
 void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
-        const int inputLength, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
+        const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
         Correction *correction, WordsPriorityQueuePool *queuePool,
         const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) const {
     uint8_t totalTraverseCount = correction->pushAndGetTotalTraverseCount();
@@ -351,7 +351,7 @@
     int childCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &rootPosition);
     int outputIndex = 0;
 
-    correction->initCorrectionState(rootPosition, childCount, (inputLength <= 0));
+    correction->initCorrectionState(rootPosition, childCount, (inputSize <= 0));
 
     // Depth first search
     while (outputIndex >= 0) {
@@ -446,7 +446,7 @@
 int UnigramDictionary::getSubStringSuggestion(
         ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
         const int *codes, const bool useFullEditDistance, Correction *correction,
-        WordsPriorityQueuePool *queuePool, const int inputLength,
+        WordsPriorityQueuePool *queuePool, const int inputSize,
         const bool hasAutoCorrectionCandidate, const int currentWordIndex,
         const int inputWordStartPos, const int inputWordLength,
         const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
@@ -497,7 +497,7 @@
     int nextWordLength = 0;
     // TODO: Optimize init suggestion
     initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
-            inputLength, correction);
+            inputSize, correction);
 
     unsigned short word[MAX_WORD_LENGTH_INTERNAL];
     int freq = getMostFrequentWordLike(
@@ -566,7 +566,7 @@
         *outputWordLength = tempOutputWordLength;
     }
 
-    if ((inputWordStartPos + inputWordLength) < inputLength) {
+    if ((inputWordStartPos + inputWordLength) < inputSize) {
         if (outputWordStartPos + nextWordLength >= MAX_WORD_LENGTH) {
             return FLAG_MULTIPLE_SUGGEST_SKIP;
         }
@@ -585,7 +585,7 @@
                         freqArray[i], wordLengthArray[i]);
             }
             AKLOGI("Split two words: freq = %d, length = %d, %d, isSpace ? %d", pairFreq,
-                    inputLength, tempOutputWordLength, isSpaceProximity);
+                    inputSize, tempOutputWordLength, isSpaceProximity);
         }
         addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue(),
                 Dictionary::KIND_CORRECTION);
@@ -595,7 +595,7 @@
 
 void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
         const int *xcoordinates, const int *ycoordinates, const int *codes,
-        const bool useFullEditDistance, const int inputLength,
+        const bool useFullEditDistance, const int inputSize,
         Correction *correction, WordsPriorityQueuePool *queuePool,
         const bool hasAutoCorrectionCandidate, const int startInputPos, const int startWordIndex,
         const int outputWordLength, int *freqArray, int *wordLengthArray,
@@ -606,11 +606,11 @@
     }
     if (startWordIndex >= 1
             && (hasAutoCorrectionCandidate
-                    || inputLength < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) {
+                    || inputSize < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) {
         // Do not suggest 3+ words if already has auto correction candidate
         return;
     }
-    for (int i = startInputPos + 1; i < inputLength; ++i) {
+    for (int i = startInputPos + 1; i < inputSize; ++i) {
         if (DEBUG_CORRECTION_FREQ) {
             AKLOGI("Multi words(%d), start in %d sep %d start out %d",
                     startWordIndex, startInputPos, i, outputWordLength);
@@ -621,7 +621,7 @@
         int inputWordStartPos = startInputPos;
         int inputWordLength = i - startInputPos;
         const int suggestionFlag = getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates,
-                codes, useFullEditDistance, correction, queuePool, inputLength,
+                codes, useFullEditDistance, correction, queuePool, inputSize,
                 hasAutoCorrectionCandidate, startWordIndex, inputWordStartPos, inputWordLength,
                 outputWordLength, true /* not used */, freqArray, wordLengthArray, outputWord,
                 &tempOutputWordLength);
@@ -638,14 +638,14 @@
         // Next word
         // Missing space
         inputWordStartPos = i;
-        inputWordLength = inputLength - i;
+        inputWordLength = inputSize - i;
         if(getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
-                useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+                useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate,
                 startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
                 false /* missing space */, freqArray, wordLengthArray, outputWord, 0)
                         != FLAG_MULTIPLE_SUGGEST_CONTINUE) {
             getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
-                    useFullEditDistance, inputLength, correction, queuePool,
+                    useFullEditDistance, inputSize, correction, queuePool,
                     hasAutoCorrectionCandidate, inputWordStartPos, startWordIndex + 1,
                     tempOutputWordLength, freqArray, wordLengthArray, outputWord);
         }
@@ -668,7 +668,7 @@
             AKLOGI("Do mistyped space correction");
         }
         getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
-                useFullEditDistance, correction, queuePool, inputLength, hasAutoCorrectionCandidate,
+                useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate,
                 startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
                 true /* mistyped space */, freqArray, wordLengthArray, outputWord, 0);
     }
@@ -676,10 +676,10 @@
 
 void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
         const int *xcoordinates, const int *ycoordinates, const int *codes,
-        const bool useFullEditDistance, const int inputLength,
+        const bool useFullEditDistance, const int inputSize,
         Correction *correction, WordsPriorityQueuePool *queuePool,
         const bool hasAutoCorrectionCandidate) const {
-    if (inputLength >= MAX_WORD_LENGTH) return;
+    if (inputSize >= MAX_WORD_LENGTH) return;
     if (DEBUG_DICT) {
         AKLOGI("--- Suggest multiple words");
     }
@@ -692,7 +692,7 @@
     const int startInputPos = 0;
     const int startWordIndex = 0;
     getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
-            useFullEditDistance, inputLength, correction, queuePool, hasAutoCorrectionCandidate,
+            useFullEditDistance, inputSize, correction, queuePool, hasAutoCorrectionCandidate,
             startInputPos, startWordIndex, outputWordLength, freqArray, wordLengthArray,
             outputWord);
 }
@@ -700,13 +700,13 @@
 // Wrapper for getMostFrequentWordLikeInner, which matches it to the previous
 // interface.
 inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex,
-        const int inputLength, Correction *correction, unsigned short *word) const {
-    uint16_t inWord[inputLength];
+        const int inputSize, Correction *correction, unsigned short *word) const {
+    uint16_t inWord[inputSize];
 
-    for (int i = 0; i < inputLength; ++i) {
+    for (int i = 0; i < inputSize; ++i) {
         inWord[i] = (uint16_t)correction->getPrimaryCharAt(startInputIndex + i);
     }
-    return getMostFrequentWordLikeInner(inWord, inputLength, word);
+    return getMostFrequentWordLikeInner(inWord, inputSize, word);
 }
 
 // This function will take the position of a character array within a CharGroup,
diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h
index 6083f01..2c66222 100644
--- a/native/jni/src/unigram_dictionary.h
+++ b/native/jni/src/unigram_dictionary.h
@@ -53,7 +53,7 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(UnigramDictionary);
     void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const int inputLength,
+            const int *ycoordinates, const int *codes, const int inputSize,
             const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
             const bool useFullEditDistance, Correction *correction,
             WordsPriorityQueuePool *queuePool) const;
@@ -72,16 +72,16 @@
             Correction *correction) const;
     void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codes, const std::map<int, int> *bigramMap,
-            const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputLength,
+            const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputSize,
             Correction *correction, WordsPriorityQueuePool *queuePool) const;
     void getSuggestionCandidates(
-            const bool useFullEditDistance, const int inputLength,
+            const bool useFullEditDistance, const int inputSize,
             const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
             Correction *correction, WordsPriorityQueuePool *queuePool, const bool doAutoCompletion,
             const int maxErrors, const int currentWordIndex) const;
     void getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
             const int *xcoordinates, const int *ycoordinates, const int *codes,
-            const bool useFullEditDistance, const int inputLength,
+            const bool useFullEditDistance, const int inputSize,
             Correction *correction, WordsPriorityQueuePool *queuePool,
             const bool hasAutoCorrectionCandidate) const;
     void onTerminal(const int freq, const TerminalAttributes& terminalAttributes,
@@ -92,21 +92,21 @@
             const uint8_t *bigramFilter, Correction *correction, int *newCount,
             int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool,
             const int currentWordIndex) const;
-    int getMostFrequentWordLike(const int startInputIndex, const int inputLength,
+    int getMostFrequentWordLike(const int startInputIndex, const int inputSize,
             Correction *correction, unsigned short *word) const;
     int getMostFrequentWordLikeInner(const uint16_t *const inWord, const int length,
             short unsigned int *outWord) const;
     int getSubStringSuggestion(
             ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
             const int *codes, const bool useFullEditDistance, Correction *correction,
-            WordsPriorityQueuePool *queuePool, const int inputLength,
+            WordsPriorityQueuePool *queuePool, const int inputSize,
             const bool hasAutoCorrectionCandidate, const int currentWordIndex,
             const int inputWordStartPos, const int inputWordLength,
             const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
             int *wordLengthArray, unsigned short *outputWord, int *outputWordLength) const;
     void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
             const int *xcoordinates, const int *ycoordinates, const int *codes,
-            const bool useFullEditDistance, const int inputLength,
+            const bool useFullEditDistance, const int inputSize,
             Correction *correction, WordsPriorityQueuePool *queuePool,
             const bool hasAutoCorrectionCandidate, const int startPos, const int startWordIndex,
             const int outputWordLength, int *freqArray, int *wordLengthArray,
diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h
index 1e4e00a..19efa5d 100644
--- a/native/jni/src/words_priority_queue.h
+++ b/native/jni/src/words_priority_queue.h
@@ -44,17 +44,16 @@
         }
     };
 
-    WordsPriorityQueue(int maxWords, int maxWordLength) :
-            MAX_WORDS((unsigned int) maxWords), MAX_WORD_LENGTH(
-                    (unsigned int) maxWordLength) {
-        mSuggestedWords = new SuggestedWord[maxWordLength];
+    WordsPriorityQueue(int maxWords, int maxWordLength)
+            : mSuggestions(), MAX_WORDS(static_cast<unsigned int>(maxWords)),
+              MAX_WORD_LENGTH(static_cast<unsigned int>(maxWordLength)),
+              mSuggestedWords(new SuggestedWord[maxWordLength]), mHighestSuggestedWord(0) {
         for (int i = 0; i < maxWordLength; ++i) {
             mSuggestedWords[i].mUsed = false;
         }
-        mHighestSuggestedWord = 0;
     }
 
-    ~WordsPriorityQueue() {
+    virtual ~WordsPriorityQueue() {
         delete[] mSuggestedWords;
     }
 
diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h
index 3888729..c5de979 100644
--- a/native/jni/src/words_priority_queue_pool.h
+++ b/native/jni/src/words_priority_queue_pool.h
@@ -24,9 +24,10 @@
 
 class WordsPriorityQueuePool {
  public:
-    WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength) {
-        // Note: using placement new() requires the caller to call the destructor explicitly.
-        mMasterQueue = new(mMasterQueueBuf) WordsPriorityQueue(mainQueueMaxWords, maxWordLength);
+    WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords, int maxWordLength)
+            // Note: using placement new() requires the caller to call the destructor explicitly.
+            : mMasterQueue(new(mMasterQueueBuf) WordsPriorityQueue(
+                      mainQueueMaxWords, maxWordLength)) {
         for (int i = 0, subQueueBufOffset = 0;
                 i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS * SUB_QUEUE_MAX_COUNT;
                 ++i, subQueueBufOffset += sizeof(WordsPriorityQueue)) {
@@ -85,11 +86,11 @@
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueuePool);
+    char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
+    char mSubQueueBuf[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS
+            * sizeof(WordsPriorityQueue)];
     WordsPriorityQueue *mMasterQueue;
     WordsPriorityQueue *mSubQueues[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
-    char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
-    char mSubQueueBuf[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS
-                      * SUB_QUEUE_MAX_COUNT * sizeof(WordsPriorityQueue)];
 };
 } // namespace latinime
 #endif // LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
diff --git a/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java
new file mode 100644
index 0000000..0094db8
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/BinaryDictIOTests.java
@@ -0,0 +1,224 @@
+/*
+ * 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 com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Unit tests for BinaryDictInputOutput
+ */
+public class BinaryDictIOTests extends AndroidTestCase {
+    private static final String TAG = BinaryDictIOTests.class.getSimpleName();
+    private static final int MAX_UNIGRAMS = 1000;
+    private static final int UNIGRAM_FREQ = 10;
+    private static final int BIGRAM_FREQ = 50;
+
+    private static final String[] CHARACTERS =
+        {
+        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+        "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
+        };
+
+    /**
+     * Generates a random word.
+     */
+    private String generateWord(final int value) {
+        final int lengthOfChars = CHARACTERS.length;
+        StringBuilder builder = new StringBuilder("a");
+        long lvalue = Math.abs((long)value);
+        while (lvalue > 0) {
+            builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]);
+            lvalue /= lengthOfChars;
+        }
+        return builder.toString();
+    }
+
+    private List<String> generateWords(final int number, final Random random) {
+        final Set<String> wordSet = CollectionUtils.newHashSet();
+        while (wordSet.size() < number) {
+            wordSet.add(generateWord(random.nextInt()));
+        }
+        return new ArrayList<String>(wordSet);
+    }
+
+    private void addUnigrams(final int number,
+            final FusionDictionary dict,
+            final List<String> words) {
+        for (int i = 0; i < number; ++i) {
+            final String word = words.get(i);
+            dict.add(word, UNIGRAM_FREQ, null);
+        }
+    }
+
+    private void addBigrams(final FusionDictionary dict,
+            final List<String> words,
+            final SparseArray<List<Integer>> sparseArray) {
+        for (int i = 0; i < sparseArray.size(); ++i) {
+            final int w1 = sparseArray.keyAt(i);
+            for (int w2 : sparseArray.valueAt(i)) {
+                dict.setBigram(words.get(w1), words.get(w2), BIGRAM_FREQ);
+            }
+        }
+    }
+
+    private long timeWritingDictToFile(final String fileName,
+            final FusionDictionary dict) {
+
+        final File file = new File(getContext().getFilesDir(), fileName);
+        long now = -1, diff = -1;
+
+        try {
+            final FileOutputStream out = new FileOutputStream(file);
+
+            now = System.currentTimeMillis();
+            BinaryDictInputOutput.writeDictionaryBinary(out, dict, 2);
+            diff = System.currentTimeMillis() - now;
+
+            out.flush();
+            out.close();
+        } catch (IOException e) {
+            Log.e(TAG, "IO exception while writing file: " + e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "UnsupportedFormatException: " + e);
+        }
+
+        return diff;
+    }
+
+    private void checkDictionary(final FusionDictionary dict,
+            final List<String> words,
+            final SparseArray<List<Integer>> bigrams) {
+        assertNotNull(dict);
+
+        // check unigram
+        for (final String word : words) {
+            final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, word);
+            assertNotNull(cg);
+        }
+
+        // check bigram
+        for (int i = 0; i < bigrams.size(); ++i) {
+            final int w1 = bigrams.keyAt(i);
+            for (final int w2 : bigrams.valueAt(i)) {
+                final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, words.get(w1));
+                assertNotNull(words.get(w1) + "," + words.get(w2), cg.getBigram(words.get(w2)));
+            }
+        }
+    }
+
+    private long timeReadingAndCheckDict(final String fileName,
+            final List<String> words,
+            final SparseArray<List<Integer>> bigrams) {
+
+        long now, diff = -1;
+
+        try {
+            final File file = new File(getContext().getFilesDir(), fileName);
+            final FileInputStream inStream = new FileInputStream(file);
+            final ByteBuffer buffer = inStream.getChannel().map(
+                    FileChannel.MapMode.READ_ONLY, 0, file.length());
+
+            now = System.currentTimeMillis();
+
+            final FusionDictionary dict =
+                    BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+
+            diff = System.currentTimeMillis() - now;
+
+            checkDictionary(dict, words, bigrams);
+            return diff;
+
+        } catch (IOException e) {
+            Log.e(TAG, "raise IOException while reading file " + e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Unsupported format: " + e);
+        }
+
+        return diff;
+    }
+
+    private String runReadAndWrite(final List<String> words,
+            final SparseArray<List<Integer>> bigrams,
+            final String message) {
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(
+                        new HashMap<String,String>(), false, false));
+
+        final String fileName = generateWord((int)System.currentTimeMillis()) + ".dict";
+
+        addUnigrams(words.size(), dict, words);
+        addBigrams(dict, words, bigrams);
+        // check original dictionary
+        checkDictionary(dict, words, bigrams);
+
+        final long write = timeWritingDictToFile(fileName, dict);
+        final long read = timeReadingAndCheckDict(fileName, words, bigrams);
+        deleteFile(fileName);
+
+        return "PROF: read=" + read + "ms, write=" + write + "ms    :" + message;
+    }
+
+    private void deleteFile(final String fileName) {
+        final File file = new File(getContext().getFilesDir(), fileName);
+        file.delete();
+    }
+
+    public void testReadAndWrite() {
+        final List<String> results = new ArrayList<String>();
+
+        final Random random = new Random(123456);
+        final List<String> words = generateWords(MAX_UNIGRAMS, random);
+        final SparseArray<List<Integer>> emptyArray = CollectionUtils.newSparseArray();
+
+        final SparseArray<List<Integer>> chain = CollectionUtils.newSparseArray();
+        for (int i = 0; i < words.size(); ++i) chain.put(i, new ArrayList<Integer>());
+        for (int i = 1; i < words.size(); ++i) chain.get(i-1).add(i);
+
+        final SparseArray<List<Integer>> star = CollectionUtils.newSparseArray();
+        final List<Integer> list0 = CollectionUtils.newArrayList();
+        star.put(0, list0);
+        for (int i = 1; i < words.size(); ++i) star.get(0).add(i);
+
+        results.add(runReadAndWrite(words, emptyArray, "only unigram"));
+        results.add(runReadAndWrite(words, chain, "chain"));
+        results.add(runReadAndWrite(words, star, "star"));
+
+        for (final String result : results) {
+            Log.d(TAG, result);
+        }
+    }
+}
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 25e1740..fbfc1da 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -27,7 +27,8 @@
 import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
 import java.util.Arrays;
 import java.util.LinkedList;
 
@@ -238,8 +239,23 @@
      */
     private static FusionDictionary readBinaryFile(final String binaryFilename)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
-        final RandomAccessFile inputFile = new RandomAccessFile(binaryFilename, "r");
-        return BinaryDictInputOutput.readDictionaryBinary(inputFile, null);
+        FileInputStream inStream = null;
+
+        try {
+            final File file = new File(binaryFilename);
+            inStream = new FileInputStream(file);
+            final ByteBuffer buffer = inStream.getChannel().map(
+                    FileChannel.MapMode.READ_ONLY, 0, file.length());
+            return BinaryDictInputOutput.readDictionaryBinary(buffer, null);
+        } finally {
+            if (inStream != null) {
+                try {
+                    inStream.close();
+                } catch (IOException e) {
+                    // do nothing
+                }
+            }
+        }
     }
 
     /**