Merge "Fix a concurrency bug."
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index bb1aa47..8880af4 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -273,7 +273,7 @@
     public void startDoubleTapShiftKeyTimer() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
         if (keyboardView != null) {
-            keyboardView.getTimerProxy().startDoubleTapShiftKeyTimer();
+            keyboardView.startDoubleTapShiftKeyTimer();
         }
     }
 
@@ -282,7 +282,7 @@
     public void cancelDoubleTapShiftKeyTimer() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
         if (keyboardView != null) {
-            keyboardView.getTimerProxy().cancelDoubleTapShiftKeyTimer();
+            keyboardView.cancelDoubleTapShiftKeyTimer();
         }
     }
 
@@ -290,7 +290,7 @@
     @Override
     public boolean isInDoubleTapShiftKeyTimeout() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
-        return keyboardView != null && keyboardView.getTimerProxy().isInDoubleTapShiftKeyTimeout();
+        return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout();
     }
 
     /**
@@ -314,10 +314,7 @@
                 R.layout.input_view, null);
 
         mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
-        if (isHardwareAcceleratedDrawingEnabled) {
-            mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
-        }
+        mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled);
         mKeyboardView.setKeyboardActionListener(mLatinIME);
 
         // This always needs to be set since the accessibility state can
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 254b20b..054c503 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -154,6 +154,12 @@
                 Color.red(color), Color.green(color), Color.blue(color));
     }
 
+    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+        if (!enabled) return;
+        // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
+        setLayerType(LAYER_TYPE_HARDWARE, null);
+    }
+
     /**
      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
      * view will re-layout itself to accommodate the keyboard.
@@ -605,4 +611,8 @@
         super.onDetachedFromWindow();
         freeOffscreenBuffer();
     }
+
+    public void deallocateMemory() {
+        freeOffscreenBuffer();
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 43baf61..6782317 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -53,6 +53,7 @@
 import com.android.inputmethod.keyboard.internal.GestureTrailsPreview;
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
 import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
 import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
 import com.android.inputmethod.latin.Constants;
@@ -179,9 +180,7 @@
     private int mGestureFloatingPreviewTextLingerTimeout;
 
     private KeyDetector mKeyDetector;
-    private final boolean mHasDistinctMultitouch;
-    private int mOldPointerCount = 1;
-    private Key mOldKey;
+    private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
 
     private final KeyTimerHandler mKeyTimerHandler;
 
@@ -193,8 +192,6 @@
         private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
         private static final int MSG_UPDATE_BATCH_INPUT = 4;
 
-        private final int mKeyRepeatStartTimeout;
-        private final int mKeyRepeatInterval;
         private final int mIgnoreAltCodeKeyTimeout;
         private final int mGestureRecognitionUpdateTime;
 
@@ -202,10 +199,6 @@
                 final TypedArray mainKeyboardViewAttr) {
             super(outerInstance);
 
-            mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
-            mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_keyRepeatInterval, 0);
             mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
             mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
@@ -224,17 +217,7 @@
                 startWhileTypingFadeinAnimation(keyboardView);
                 break;
             case MSG_REPEAT_KEY:
-                final Key currentKey = tracker.getKey();
-                final int code = msg.arg1;
-                if (currentKey != null && currentKey.mCode == code) {
-                    startKeyRepeatTimer(tracker, mKeyRepeatInterval);
-                    startTypingStateTimer(currentKey);
-                    final KeyboardActionListener listener =
-                            keyboardView.getKeyboardActionListener();
-                    listener.onPressKey(code, true /* isRepeatKey */, true /* isSinglePointer */);
-                    listener.onCodeInput(code,
-                            Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-                }
+                tracker.onKeyRepeat(msg.arg1);
                 break;
             case MSG_LONGPRESS_KEY:
                 keyboardView.onLongPress(tracker);
@@ -246,19 +229,15 @@
             }
         }
 
-        private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) {
+        @Override
+        public void startKeyRepeatTimer(final PointerTracker tracker, final int delay) {
             final Key key = tracker.getKey();
-            if (key == null) {
+            if (key == null || delay == 0) {
                 return;
             }
             sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
         }
 
-        @Override
-        public void startKeyRepeatTimer(final PointerTracker tracker) {
-            startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
-        }
-
         public void cancelKeyRepeatTimer() {
             removeMessages(MSG_REPEAT_KEY);
         }
@@ -443,13 +422,16 @@
     public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
         super(context, attrs, defStyle);
 
+        PointerTracker.init(getResources());
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
         final boolean forceNonDistinctMultitouch = prefs.getBoolean(
                 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
         final boolean hasDistinctMultitouch = context.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
-        mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch;
-        PointerTracker.init(getResources());
+                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
+                && !forceNonDistinctMultitouch;
+        mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
+                : new NonDistinctMultitouchHelper();
+
         mPreviewPlacerView = new PreviewPlacerView(context, attrs);
 
         final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
@@ -530,6 +512,12 @@
         mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
     }
 
+    @Override
+    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+        super.setHardwareAcceleratedDrawingEnabled(enabled);
+        mPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
+    }
+
     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
         if (resId == 0) {
             // TODO: Stop returning null.
@@ -1027,6 +1015,18 @@
         }
     }
 
+    public void startDoubleTapShiftKeyTimer() {
+        mKeyTimerHandler.startDoubleTapShiftKeyTimer();
+    }
+
+    public void cancelDoubleTapShiftKeyTimer() {
+        mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
+    }
+
+    public boolean isInDoubleTapShiftKeyTimeout() {
+        return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent event) {
         if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
@@ -1040,100 +1040,31 @@
         if (getKeyboard() == null) {
             return false;
         }
-        // TODO: Add multi-touch to single-touch event converter for non-distinct multi-touch
-        // device.
+        if (mNonDistinctMultitouchHelper != null) {
+            if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
+                // Key repeating timer will be canceled if 2 or more keys are in action.
+                mKeyTimerHandler.cancelKeyRepeatTimer();
+            }
+            // Non distinct multitouch screen support
+            mNonDistinctMultitouchHelper.processMotionEvent(me, this);
+            return true;
+        }
         return processMotionEvent(me);
     }
 
     public boolean processMotionEvent(final MotionEvent me) {
-        final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
-        final int action = me.getActionMasked();
-        final int pointerCount = me.getPointerCount();
-        final int oldPointerCount = mOldPointerCount;
-        mOldPointerCount = pointerCount;
-
-        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
-        // If the device does not have distinct multi-touch support panel, ignore all multi-touch
-        // events except a transition from/to single-touch.
-        if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
-            return true;
-        }
-
-        final long eventTime = me.getEventTime();
-        final int index = me.getActionIndex();
-        final int id = me.getPointerId(index);
-        final int x = (int)me.getX(index);
-        final int y = (int)me.getY(index);
-
-        // TODO: This might be moved to the tracker.processMotionEvent() call below.
         if (LatinImeLogger.sUsabilityStudy) {
             UsabilityStudyLogUtils.writeMotionEvent(me);
         }
-        // TODO: This should be moved to the tracker.processMotionEvent() call below.
         // Currently the same "move" event is being logged twice.
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.mainKeyboardView_processMotionEvent(
-                    me, action, eventTime, index, id, x, y);
+            ResearchLogger.mainKeyboardView_processMotionEvent(me);
         }
 
-        if (mKeyTimerHandler.isInKeyRepeat()) {
-            final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
-            // Key repeating timer will be canceled if 2 or more keys are in action, and current
-            // event (UP or DOWN) is non-modifier key.
-            if (pointerCount > 1 && !tracker.isModifier()) {
-                mKeyTimerHandler.cancelKeyRepeatTimer();
-            }
-            // Up event will pass through.
-        }
-
-        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
-        // Translate mutli-touch event to single-touch events on the device that has no distinct
-        // multi-touch panel.
-        if (nonDistinctMultitouch) {
-            // Use only main (id=0) pointer tracker.
-            final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
-            if (pointerCount == 1 && oldPointerCount == 2) {
-                // Multi-touch to single touch transition.
-                // Send a down event for the latest pointer if the key is different from the
-                // previous key.
-                final Key newKey = tracker.getKeyOn(x, y);
-                if (mOldKey != newKey) {
-                    tracker.onDownEvent(x, y, eventTime, this);
-                    if (action == MotionEvent.ACTION_UP) {
-                        tracker.onUpEvent(x, y, eventTime);
-                    }
-                }
-            } else if (pointerCount == 2 && oldPointerCount == 1) {
-                // Single-touch to multi-touch transition.
-                // Send an up event for the last pointer.
-                final int[] lastCoords = CoordinateUtils.newInstance();
-                mOldKey = tracker.getKeyOn(
-                        CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords));
-                tracker.onUpEvent(
-                        CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime);
-            } else if (pointerCount == 1 && oldPointerCount == 1) {
-                tracker.processMotionEvent(action, x, y, eventTime, this);
-            } else {
-                Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
-                        + " (old " + oldPointerCount + ")");
-            }
-            return true;
-        }
-
-        if (action == MotionEvent.ACTION_MOVE) {
-            for (int i = 0; i < pointerCount; i++) {
-                final int pointerId = me.getPointerId(i);
-                final PointerTracker tracker = PointerTracker.getPointerTracker(
-                        pointerId, this);
-                final int px = (int)me.getX(i);
-                final int py = (int)me.getY(i);
-                tracker.onMoveEvent(px, py, eventTime, me);
-            }
-        } else {
-            final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
-            tracker.processMotionEvent(action, x, y, eventTime, this);
-        }
-
+        final int index = me.getActionIndex();
+        final int id = me.getPointerId(index);
+        final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
+        tracker.processMotionEvent(me, this);
         return true;
     }
 
@@ -1330,7 +1261,9 @@
         }
     }
 
+    @Override
     public void deallocateMemory() {
+        super.deallocateMemory();
         mGestureTrailsPreview.deallocateMemory();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 20fc109..ab5fee9 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -94,7 +94,7 @@
     public interface TimerProxy {
         public void startTypingStateTimer(Key typedKey);
         public boolean isTypingState();
-        public void startKeyRepeatTimer(PointerTracker tracker);
+        public void startKeyRepeatTimer(PointerTracker tracker, int delay);
         public void startLongPressTimer(PointerTracker tracker, int delay);
         public void cancelLongPressTimer();
         public void startDoubleTapShiftKeyTimer();
@@ -111,7 +111,7 @@
             @Override
             public boolean isTypingState() { return false; }
             @Override
-            public void startKeyRepeatTimer(PointerTracker tracker) {}
+            public void startKeyRepeatTimer(PointerTracker tracker, int delay) {}
             @Override
             public void startLongPressTimer(PointerTracker tracker, int delay) {}
             @Override
@@ -138,6 +138,8 @@
         public final int mTouchNoiseThresholdTime;
         public final int mTouchNoiseThresholdDistance;
         public final int mSuppressKeyPreviewAfterBatchInputDuration;
+        public final int mKeyRepeatStartTimeout;
+        public final int mKeyRepeatInterval;
         public final int mLongPressShiftLockTimeout;
 
         public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
@@ -147,6 +149,8 @@
             mTouchNoiseThresholdTime = 0;
             mTouchNoiseThresholdDistance = 0;
             mSuppressKeyPreviewAfterBatchInputDuration = 0;
+            mKeyRepeatStartTimeout = 0;
+            mKeyRepeatInterval = 0;
             mLongPressShiftLockTimeout = 0;
         }
 
@@ -159,6 +163,10 @@
                     R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
             mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
+            mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
+            mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_keyRepeatInterval, 0);
             mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
         }
@@ -470,6 +478,10 @@
         mPointerId = id;
         mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(
                 id, sGestureStrokeParams, sGesturePreviewParams);
+        setKeyEventHandler(handler);
+    }
+
+    private void setKeyEventHandler(final KeyEventHandler handler) {
         setKeyDetectorInner(handler.getKeyDetector());
         mListener = handler.getKeyboardActionListener();
         mDrawingProxy = handler.getDrawingProxy();
@@ -477,7 +489,8 @@
     }
 
     // Returns true if keyboard has been changed by this callback.
-    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
+    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
+            final boolean isRepeatKey) {
         // While gesture input is going on, this method should be a no-operation. But when gesture
         // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
         // are set to false. To keep this method is a no-operation,
@@ -487,17 +500,17 @@
         }
         final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
         if (DEBUG_LISTENER) {
-            Log.d(TAG, String.format("[%d] onPress    : %s%s%s", mPointerId,
+            Log.d(TAG, String.format("[%d] onPress    : %s%s%s%s", mPointerId,
                     KeyDetector.printableCode(key),
                     ignoreModifierKey ? " ignoreModifier" : "",
-                    key.isEnabled() ? "" : " disabled"));
+                    key.isEnabled() ? "" : " disabled",
+                    isRepeatKey ? " repeat" : ""));
         }
         if (ignoreModifierKey) {
             return false;
         }
         if (key.isEnabled()) {
-            mListener.onPressKey(key.mCode, false /* isRepeatKey */,
-                    getActivePointerTrackerCount() == 1);
+            mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1);
             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
             mKeyboardLayoutHasBeenChanged = false;
             mTimerProxy.startTypingStateTimer(key);
@@ -857,8 +870,23 @@
         mListener.onCancelBatchInput();
     }
 
-    public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
-            final KeyEventHandler handler) {
+    public void processMotionEvent(final MotionEvent me, final KeyEventHandler handler) {
+        final int action = me.getActionMasked();
+        final long eventTime = me.getEventTime();
+        if (action == MotionEvent.ACTION_MOVE) {
+            final int pointerCount = me.getPointerCount();
+            for (int index = 0; index < pointerCount; index++) {
+                final int id = me.getPointerId(index);
+                final PointerTracker tracker = getPointerTracker(id, handler);
+                final int x = (int)me.getX(index);
+                final int y = (int)me.getY(index);
+                tracker.onMoveEvent(x, y, eventTime, me);
+            }
+            return;
+        }
+        final int index = me.getActionIndex();
+        final int x = (int)me.getX(index);
+        final int y = (int)me.getY(index);
         switch (action) {
         case MotionEvent.ACTION_DOWN:
         case MotionEvent.ACTION_POINTER_DOWN:
@@ -868,24 +896,18 @@
         case MotionEvent.ACTION_POINTER_UP:
             onUpEvent(x, y, eventTime);
             break;
-        case MotionEvent.ACTION_MOVE:
-            onMoveEvent(x, y, eventTime, null);
-            break;
         case MotionEvent.ACTION_CANCEL:
             onCancelEvent(x, y, eventTime);
             break;
         }
     }
 
-    public void onDownEvent(final int x, final int y, final long eventTime,
+    private void onDownEvent(final int x, final int y, final long eventTime,
             final KeyEventHandler handler) {
         if (DEBUG_EVENT) {
             printTouchEvent("onDownEvent:", x, y, eventTime);
         }
-        mDrawingProxy = handler.getDrawingProxy();
-        mTimerProxy = handler.getTimerProxy();
-        setKeyboardActionListener(handler.getKeyboardActionListener());
-        setKeyDetectorInner(handler.getKeyDetector());
+        setKeyEventHandler(handler);
         // Naive up-to-down noise filter.
         final long deltaT = eventTime - mUpTime;
         if (deltaT < sParams.mTouchNoiseThresholdTime) {
@@ -917,7 +939,7 @@
         }
         // A gesture should start only from a non-modifier key.
         mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
-                && key != null && !key.isModifier();
+                && key != null && !key.isModifier() && !key.isRepeatable();
         if (mIsDetectingGesture) {
             if (getActivePointerTrackerCount() == 1) {
                 sGestureFirstDownTime = eventTime;
@@ -945,7 +967,7 @@
             // This onPress call may have changed keyboard layout. Those cases are detected at
             // {@link #setKeyboard}. In those cases, we should update key according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
                 key = onDownKey(x, y, eventTime);
             }
 
@@ -995,7 +1017,7 @@
         }
     }
 
-    public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
+    private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
         if (DEBUG_MOVE_EVENT) {
             printTouchEvent("onMoveEvent:", x, y, eventTime);
         }
@@ -1035,7 +1057,7 @@
         // at {@link #setKeyboard}. In those cases, we should update key according
         // to the new keyboard layout.
         Key key = newKey;
-        if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+        if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
             key = onMoveKey(x, y);
         }
         onMoveToNewKey(key, x, y);
@@ -1183,7 +1205,7 @@
         }
     }
 
-    public void onUpEvent(final int x, final int y, final long eventTime) {
+    private void onUpEvent(final int x, final int y, final long eventTime) {
         if (DEBUG_EVENT) {
             printTouchEvent("onUpEvent  :", x, y, eventTime);
         }
@@ -1283,7 +1305,7 @@
         sPointerTrackerQueue.remove(this);
     }
 
-    public void onCancelEvent(final int x, final int y, final long eventTime) {
+    private void onCancelEvent(final int x, final int y, final long eventTime) {
         if (DEBUG_EVENT) {
             printTouchEvent("onCancelEvt:", x, y, eventTime);
         }
@@ -1304,16 +1326,6 @@
         }
     }
 
-    private void startRepeatKey(final Key key) {
-        if (sInGesture) return;
-        if (key == null) return;
-        if (!key.isRepeatable()) return;
-        // Don't start key repeat when we are in sliding input mode.
-        if (mIsInSlidingKeyInput) return;
-        detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
-        mTimerProxy.startKeyRepeatTimer(this);
-    }
-
     private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
             final Key newKey) {
         if (mKeyDetector == null) {
@@ -1394,6 +1406,26 @@
         callListenerOnRelease(key, code, false /* withSliding */);
     }
 
+    private void startRepeatKey(final Key key) {
+        if (sInGesture) return;
+        if (key == null) return;
+        if (!key.isRepeatable()) return;
+        // Don't start key repeat when we are in sliding input mode.
+        if (mIsInSlidingKeyInput) return;
+        detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
+        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatStartTimeout);
+    }
+
+    public void onKeyRepeat(final int code) {
+        final Key key = getKey();
+        if (key == null || key.mCode != code) {
+            return;
+        }
+        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval);
+        callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */);
+        callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
+    }
+
     private void printTouchEvent(final String title, final int x, final int y,
             final long eventTime) {
         final Key key = mKeyDetector.detectHitKey(x, y);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
index d4c2594..19e9955 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
@@ -110,6 +110,7 @@
 
     private void freeOffscreenBuffer() {
         mOffscreenCanvas.setBitmap(null);
+        mOffscreenCanvas.setMatrix(null);
         if (mOffscreenBuffer != null) {
             mOffscreenBuffer.recycle();
             mOffscreenBuffer = null;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
new file mode 100644
index 0000000..a0935b9
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2013 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.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+
+public final class NonDistinctMultitouchHelper {
+    private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName();
+
+    private int mOldPointerCount = 1;
+    private Key mOldKey;
+    private int[] mLastCoords = CoordinateUtils.newInstance();
+
+    public void processMotionEvent(final MotionEvent me, final KeyEventHandler keyEventHandler) {
+        final int pointerCount = me.getPointerCount();
+        final int oldPointerCount = mOldPointerCount;
+        mOldPointerCount = pointerCount;
+        // Ignore continuous multi-touch events because we can't trust the coordinates
+        // in multi-touch events.
+        if (pointerCount > 1 && oldPointerCount > 1) {
+            return;
+        }
+
+        // Use only main (id=0) pointer tracker.
+        final PointerTracker mainTracker = PointerTracker.getPointerTracker(0, keyEventHandler);
+        final int action = me.getActionMasked();
+        final int index = me.getActionIndex();
+        final long eventTime = me.getEventTime();
+        final long downTime = me.getDownTime();
+
+        // In single-touch.
+        if (oldPointerCount == 1 && pointerCount == 1) {
+            if (me.getPointerId(index) == mainTracker.mPointerId) {
+                mainTracker.processMotionEvent(me, keyEventHandler);
+                return;
+            }
+            // Inject a copied event.
+            injectMotionEvent(action, me.getX(index), me.getY(index), downTime, eventTime,
+                    mainTracker, keyEventHandler);
+            return;
+        }
+
+        // Single-touch to multi-touch transition.
+        if (oldPointerCount == 1 && pointerCount == 2) {
+            // Send an up event for the last pointer, be cause we can't trust the coordinates of
+            // this multi-touch event.
+            mainTracker.getLastCoordinates(mLastCoords);
+            final int x = CoordinateUtils.x(mLastCoords);
+            final int y = CoordinateUtils.y(mLastCoords);
+            mOldKey = mainTracker.getKeyOn(x, y);
+            // Inject an artifact up event for the old key.
+            injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
+                    mainTracker, keyEventHandler);
+            return;
+        }
+
+        // Multi-touch to single-touch transition.
+        if (oldPointerCount == 2 && pointerCount == 1) {
+            // Send a down event for the latest pointer if the key is different from the previous
+            // key.
+            final int x = (int)me.getX(index);
+            final int y = (int)me.getY(index);
+            final Key newKey = mainTracker.getKeyOn(x, y);
+            if (mOldKey != newKey) {
+                // Inject an artifact down event for the new key.
+                // An artifact up event for the new key will usually be injected as a single-touch.
+                injectMotionEvent(MotionEvent.ACTION_DOWN, x, y, downTime, eventTime,
+                        mainTracker, keyEventHandler);
+                if (action == MotionEvent.ACTION_UP) {
+                    // Inject an artifact up event for the new key also.
+                    injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
+                            mainTracker, keyEventHandler);
+                }
+            }
+            return;
+        }
+
+        Log.w(TAG, "Unknown touch panel behavior: pointer count is "
+                + pointerCount + " (previously " + oldPointerCount + ")");
+    }
+
+    private static void injectMotionEvent(final int action, final float x, final float y,
+            final long downTime, final long eventTime, final PointerTracker tracker,
+            final KeyEventHandler handler) {
+        final MotionEvent me = MotionEvent.obtain(
+                downTime, eventTime, action, x, y, 0 /* metaState */);
+        try {
+            tracker.processMotionEvent(me, handler);
+        } finally {
+            me.recycle();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 3388c57..4c8607d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -37,7 +37,10 @@
     public PreviewPlacerView(final Context context, final AttributeSet attrs) {
         super(context, attrs);
         setWillNotDraw(false);
+    }
 
+    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+        if (!enabled) return;
         final Paint layerPaint = new Paint();
         layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
         setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 614c143..719c7c8 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -76,6 +76,7 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.personalization.PersonalizationDictionaryHelper;
+import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
 import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsActivity;
@@ -170,6 +171,7 @@
     private boolean mIsMainDictionaryAvailable;
     private UserBinaryDictionary mUserDictionary;
     private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
+    private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -560,6 +562,9 @@
         mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper
                 .getUserHistoryPredictionDictionary(this, localeStr, prefs);
         newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+        mPersonalizationPredictionDictionary = PersonalizationDictionaryHelper
+                .getPersonalizationPredictionDictionary(this, localeStr, prefs);
+        newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
 
         final Suggest oldSuggest = mSuggest;
         resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
@@ -2509,9 +2514,9 @@
         final SettingsValues currentSettings = mSettings.getCurrent();
         if (!currentSettings.mCorrectionEnabled) return null;
 
-        final UserHistoryPredictionDictionary userHistoryDictionary =
+        final UserHistoryPredictionDictionary userHistoryPredictionDictionary =
                 mUserHistoryPredictionDictionary;
-        if (userHistoryDictionary == null) return null;
+        if (userHistoryPredictionDictionary == null) return null;
 
         final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
         final String secondWord;
@@ -2525,7 +2530,7 @@
         final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
                 suggest.getUnigramDictionaries(), suggestion);
         if (maxFreq == 0) return null;
-        userHistoryDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
+        userHistoryPredictionDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
         return prevWord;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 6b01667..2879e2e 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,6 +22,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
 import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
 import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
 import com.android.inputmethod.latin.utils.BoundedTreeSet;
@@ -174,6 +175,12 @@
                 userHistoryPredictionDictionary);
     }
 
+    public void setPersonalizationPredictionDictionary(
+            final PersonalizationPredictionDictionary personalizationPredictionDictionary) {
+        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
+                personalizationPredictionDictionary);
+    }
+
     public void setAutoCorrectionThreshold(float threshold) {
         mAutoCorrectionThreshold = threshold;
     }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
index f5dae99..9f013df 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
@@ -30,25 +30,52 @@
     private static final boolean DEBUG = false;
 
     private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
-            sLangDictCache = CollectionUtils.newConcurrentHashMap();
+            sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
+
+    private static final ConcurrentHashMap<String,
+            SoftReference<PersonalizationPredictionDictionary>>
+                    sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
 
     public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
-        synchronized (sLangDictCache) {
-            if (sLangDictCache.containsKey(locale)) {
+        synchronized (sLangUserHistoryDictCache) {
+            if (sLangUserHistoryDictCache.containsKey(locale)) {
                 final SoftReference<UserHistoryPredictionDictionary> ref =
-                        sLangDictCache.get(locale);
+                        sLangUserHistoryDictCache.get(locale);
                 final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get();
                 if (dict != null) {
                     if (DEBUG) {
-                        Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
+                        Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale);
                     }
                     return dict;
                 }
             }
             final UserHistoryPredictionDictionary dict =
                     new UserHistoryPredictionDictionary(context, locale, sp);
-            sLangDictCache.put(locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
+            sLangUserHistoryDictCache.put(
+                    locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
+            return dict;
+        }
+    }
+
+    public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
+            final Context context, final String locale, final SharedPreferences sp) {
+        synchronized (sLangPersonalizationDictCache) {
+            if (sLangPersonalizationDictCache.containsKey(locale)) {
+                final SoftReference<PersonalizationPredictionDictionary> ref =
+                        sLangPersonalizationDictCache.get(locale);
+                final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
+                if (dict != null) {
+                    if (DEBUG) {
+                        Log.w(TAG, "Use cached PersonalizationPredictionDictionary for " + locale);
+                    }
+                    return dict;
+                }
+            }
+            final PersonalizationPredictionDictionary dict =
+                    new PersonalizationPredictionDictionary(context, locale, sp);
+            sLangPersonalizationDictCache.put(
+                    locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
             return dict;
         }
     }
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index fc8615b..25187ced 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1066,22 +1066,24 @@
     private static final LogStatement LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT =
             new LogStatement("MotionEvent", true, false, "action",
                     LogStatement.KEY_IS_LOGGING_RELATED, "motionEvent");
-    public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action,
-            final long eventTime, final int index, final int id, final int x, final int y) {
-        if (me != null) {
-            final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
-            final ResearchLogger researchLogger = getInstance();
-            researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
-                    actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
-            if (action == MotionEvent.ACTION_DOWN) {
-                // Subtract 1 from eventTime so the down event is included in the later
-                // LogUnit, not the earlier (the test is for inequality).
-                researchLogger.setSavedDownEventTime(eventTime - 1);
-            }
-            // Refresh the timer in case we are capturing user feedback.
-            if (researchLogger.isMakingUserRecording()) {
-                researchLogger.resetRecordingTimer();
-            }
+    public static void mainKeyboardView_processMotionEvent(final MotionEvent me) {
+        if (me == null) {
+            return;
+        }
+        final int action = me.getActionMasked();
+        final long eventTime = me.getEventTime();
+        final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
+                actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
+        if (action == MotionEvent.ACTION_DOWN) {
+            // Subtract 1 from eventTime so the down event is included in the later
+            // LogUnit, not the earlier (the test is for inequality).
+            researchLogger.setSavedDownEventTime(eventTime - 1);
+        }
+        // Refresh the timer in case we are capturing user feedback.
+        if (researchLogger.isMakingUserRecording()) {
+            researchLogger.resetRecordingTimer();
         }
     }
 
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 771623c..acd230f 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -72,7 +72,8 @@
     suggest/core/session/dic_traverse_session.cpp \
     $(addprefix suggest/policyimpl/dictionary/, \
         dynamic_patricia_trie_policy.cpp \
-        patricia_trie_policy.cpp) \
+        patricia_trie_policy.cpp \
+        patricia_trie_reading_utils.cpp) \
     suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp \
     $(addprefix suggest/policyimpl/typing/, \
         scoring_params.cpp \
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp
index 52b6689..20b77b3 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp
@@ -44,15 +44,15 @@
     const int origin = *pos;
     switch (MASK_ATTRIBUTE_ADDRESS_TYPE & flags) {
         case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
-            offset = ByteArrayUtils::readUint8andAdvancePosition(
+            offset = ByteArrayUtils::readUint8AndAdvancePosition(
                     binaryDictionaryInfo->getDictRoot(), pos);
             break;
         case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
-            offset = ByteArrayUtils::readUint16andAdvancePosition(
+            offset = ByteArrayUtils::readUint16AndAdvancePosition(
                     binaryDictionaryInfo->getDictRoot(), pos);
             break;
         case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
-            offset = ByteArrayUtils::readUint24andAdvancePosition(
+            offset = ByteArrayUtils::readUint24AndAdvancePosition(
                     binaryDictionaryInfo->getDictRoot(), pos);
             break;
     }
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h
index 15637d8..375fc7d 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h
@@ -33,7 +33,7 @@
 
     static AK_FORCE_INLINE TerminalAttributeFlags getFlagsAndForwardPointer(
             const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
-        return ByteArrayUtils::readUint8andAdvancePosition(
+        return ByteArrayUtils::readUint8AndAdvancePosition(
                 binaryDictionaryInfo->getDictRoot(), pos);
     }
 
@@ -66,7 +66,7 @@
     static AK_FORCE_INLINE int getShortcutListSizeAndForwardPointer(
             const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
         // readUint16andAdvancePosition() returns an offset *including* the uint16 field itself.
-        return ByteArrayUtils::readUint16andAdvancePosition(
+        return ByteArrayUtils::readUint16AndAdvancePosition(
                 binaryDictionaryInfo->getDictRoot(), pos) - SHORTCUT_LIST_SIZE_FIELD_SIZE;
     }
 
diff --git a/native/jni/src/suggest/core/dictionary/byte_array_utils.h b/native/jni/src/suggest/core/dictionary/byte_array_utils.h
index daa822f..75ccfc7 100644
--- a/native/jni/src/suggest/core/dictionary/byte_array_utils.h
+++ b/native/jni/src/suggest/core/dictionary/byte_array_utils.h
@@ -50,39 +50,39 @@
         return buffer[pos];
     }
 
-    static AK_FORCE_INLINE uint32_t readUint32andAdvancePosition(
+    static AK_FORCE_INLINE uint32_t readUint32AndAdvancePosition(
             const uint8_t *const buffer, int *const pos) {
         const uint32_t value = readUint32(buffer, *pos);
         *pos += 4;
         return value;
     }
 
-    static AK_FORCE_INLINE int readSint24andAdvancePosition(
+    static AK_FORCE_INLINE int readSint24AndAdvancePosition(
             const uint8_t *const buffer, int *const pos) {
         const uint8_t value = readUint8(buffer, *pos);
         if (value < 0x80) {
-            return readUint24andAdvancePosition(buffer, pos);
+            return readUint24AndAdvancePosition(buffer, pos);
         } else {
             (*pos)++;
-            return -(((value & 0x7F) << 16) ^ readUint16andAdvancePosition(buffer, pos));
+            return -(((value & 0x7F) << 16) ^ readUint16AndAdvancePosition(buffer, pos));
         }
     }
 
-    static AK_FORCE_INLINE uint32_t readUint24andAdvancePosition(
+    static AK_FORCE_INLINE uint32_t readUint24AndAdvancePosition(
             const uint8_t *const buffer, int *const pos) {
         const uint32_t value = readUint24(buffer, *pos);
         *pos += 3;
         return value;
     }
 
-    static AK_FORCE_INLINE uint16_t readUint16andAdvancePosition(
+    static AK_FORCE_INLINE uint16_t readUint16AndAdvancePosition(
             const uint8_t *const buffer, int *const pos) {
         const uint16_t value = readUint16(buffer, *pos);
         *pos += 2;
         return value;
     }
 
-    static AK_FORCE_INLINE uint8_t readUint8andAdvancePosition(
+    static AK_FORCE_INLINE uint8_t readUint8AndAdvancePosition(
             const uint8_t *const buffer, int *const pos) {
         return buffer[(*pos)++];
     }
@@ -113,7 +113,7 @@
                 *pos += 1;
                 return NOT_A_CODE_POINT;
             } else {
-                return readUint24andAdvancePosition(buffer, pos);
+                return readUint24AndAdvancePosition(buffer, pos);
             }
         } else {
             *pos += 1;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/binary_format.h b/native/jni/src/suggest/policyimpl/dictionary/binary_format.h
index 9e22b50..23f4c7f 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/binary_format.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/binary_format.h
@@ -56,7 +56,6 @@
     // Mask and flags for attribute address type selection.
     static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
 
-    static bool hasBlacklistedOrNotAWordFlag(const int flags);
     static int getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos);
     static uint8_t getFlagsAndForwardPointer(const uint8_t *const dict, int *pos);
     static int getCodePointAndForwardPointer(const uint8_t *const dict, int *pos);
@@ -74,10 +73,6 @@
     static int getCodePointsAndProbabilityAndReturnCodePointCount(
             const uint8_t *const root, const int nodePos, const int maxCodePointCount,
             int *const outCodePoints, int *const outUnigramProbability);
-    static int getBigramListPositionForWordPosition(const uint8_t *const root,
-            const int nodePosition);
-    static int getShortcutListPositionForWordPosition(const uint8_t *const root,
-            const int nodePosition);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
@@ -99,10 +94,6 @@
     static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
 };
 
-inline bool BinaryFormat::hasBlacklistedOrNotAWordFlag(const int flags) {
-    return (flags & (FLAG_IS_BLACKLISTED | FLAG_IS_NOT_A_WORD)) != 0;
-}
-
 AK_FORCE_INLINE int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t *const dict,
         int *pos) {
     const int msb = dict[(*pos)++];
@@ -475,38 +466,5 @@
     return 0;
 }
 
-AK_FORCE_INLINE int BinaryFormat::getBigramListPositionForWordPosition(
-        const uint8_t *const root, const int nodePosition) {
-    if (NOT_A_VALID_WORD_POS == nodePosition) return NOT_A_DICT_POS;
-    int position = nodePosition;
-    const uint8_t flags = getFlagsAndForwardPointer(root, &position);
-    if (!(flags & FLAG_HAS_BIGRAMS)) return NOT_A_DICT_POS;
-    if (flags & FLAG_HAS_MULTIPLE_CHARS) {
-        position = skipOtherCharacters(root, position);
-    } else {
-        getCodePointAndForwardPointer(root, &position);
-    }
-    position = skipProbability(flags, position);
-    position = skipChildrenPosition(flags, position);
-    position = skipShortcuts(root, flags, position);
-    return position;
-}
-
-AK_FORCE_INLINE int BinaryFormat::getShortcutListPositionForWordPosition(
-        const uint8_t *const root, const int nodePosition) {
-    if (NOT_A_VALID_WORD_POS == nodePosition) return NOT_A_DICT_POS;
-    int position = nodePosition;
-    const uint8_t flags = getFlagsAndForwardPointer(root, &position);
-    if (!(flags & FLAG_HAS_SHORTCUT_TARGETS)) return NOT_A_DICT_POS;
-    if (flags & FLAG_HAS_MULTIPLE_CHARS) {
-        position = skipOtherCharacters(root, position);
-    } else {
-        getCodePointAndForwardPointer(root, &position);
-    }
-    position = skipProbability(flags, position);
-    position = skipChildrenPosition(flags, position);
-    return position;
-}
-
 } // namespace latinime
 #endif // LATINIME_BINARY_FORMAT_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
new file mode 100644
index 0000000..0de6341
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+typedef DynamicPatriciaTrieReadingUtils DptReadingUtils;
+
+const DptReadingUtils::NodeFlags DptReadingUtils::MASK_MOVED = 0xC0;
+const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_NOT_MOVED = 0xC0;
+const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_MOVED = 0x40;
+const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_DELETED = 0x80;
+
+/* static */ int DptReadingUtils::readChildrenPositionAndAdvancePosition(
+        const uint8_t *const buffer, const NodeFlags flags, int *const pos) {
+    if ((flags & MASK_MOVED) == FLAG_IS_NOT_MOVED) {
+        const int base = *pos;
+        return base + ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+    } else {
+        return NOT_A_DICT_POS;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
new file mode 100644
index 0000000..f44c265
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013, 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_DYNAMIC_PATRICIA_TRIE_READING_UTILS_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_READING_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+class DynamicPatriciaTrieReadingUtils {
+ public:
+    typedef uint8_t NodeFlags;
+
+    static AK_FORCE_INLINE int getForwardLinkPosition(const uint8_t *const buffer, const int pos) {
+        int linkAddressPos = pos;
+        return ByteArrayUtils::readSint24AndAdvancePosition(buffer, &linkAddressPos);
+    }
+
+    static AK_FORCE_INLINE bool isValidForwardLinkPosition(const int forwardLinkAddress) {
+        return forwardLinkAddress != 0;
+    }
+
+    static AK_FORCE_INLINE int getParentPosAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos) {
+        const int base = *pos;
+        return base + ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+    }
+
+    static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer,
+            const NodeFlags flags, int *const pos);
+
+    /**
+     * Node Flags
+     */
+    static AK_FORCE_INLINE bool isMoved(const NodeFlags flags) {
+        return FLAG_IS_MOVED == (MASK_MOVED & flags);
+    }
+
+    static AK_FORCE_INLINE bool isDeleted(const NodeFlags flags) {
+        return FLAG_IS_DELETED == (MASK_MOVED & flags);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieReadingUtils);
+
+    static const NodeFlags MASK_MOVED;
+    static const NodeFlags FLAG_IS_NOT_MOVED;
+    static const NodeFlags FLAG_IS_MOVED;
+    static const NodeFlags FLAG_IS_DELETED;
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_READING_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
index 2a9a5ce..097f7c8 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
@@ -21,7 +21,9 @@
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
 #include "suggest/policyimpl/dictionary/binary_format.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
 
 namespace latinime {
 
@@ -34,7 +36,7 @@
         return;
     }
     int nextPos = dicNode->getChildrenPos();
-    const int childCount = BinaryFormat::getGroupCountAndForwardPointer(
+    const int childCount = PatriciaTrieReadingUtils::getGroupCountAndAdvancePosition(
             binaryDictionaryInfo->getDictRoot(), &nextPos);
     for (int i = 0; i < childCount; i++) {
         nextPos = createAndGetLeavingChildNode(dicNode, nextPos, binaryDictionaryInfo,
@@ -60,82 +62,108 @@
 
 int PatriciaTriePolicy::getUnigramProbability(
         const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos) const {
-    const uint8_t *const root = binaryDictionaryInfo->getDictRoot();
+    if (nodePos == NOT_A_VALID_WORD_POS) {
+        return NOT_A_PROBABILITY;
+    }
+    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
     int pos = nodePos;
-    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-    if (flags & (BinaryFormat::FLAG_IS_BLACKLISTED | BinaryFormat::FLAG_IS_NOT_A_WORD)) {
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+    if (!PatriciaTrieReadingUtils::isTerminal(flags)) {
+        return NOT_A_PROBABILITY;
+    }
+    if (PatriciaTrieReadingUtils::isNotAWord(flags)
+            || PatriciaTrieReadingUtils::isBlacklisted(flags)) {
         // If this is not a word, or if it's a blacklisted entry, it should behave as
         // having no probability outside of the suggestion process (where it should be used
         // for shortcuts).
         return NOT_A_PROBABILITY;
     }
-    const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags));
-    if (hasMultipleChars) {
-        pos = BinaryFormat::skipOtherCharacters(root, pos);
-    } else {
-        BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-    }
-    return BinaryFormat::readProbabilityWithoutMovingPointer(root, pos);
+    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
+    return PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
 }
 
 int PatriciaTriePolicy::getShortcutPositionOfNode(
         const BinaryDictionaryInfo *const binaryDictionaryInfo,
         const int nodePos) const {
-    return BinaryFormat::getShortcutListPositionForWordPosition(
-            binaryDictionaryInfo->getDictRoot(), nodePos);
+    if (nodePos == NOT_A_VALID_WORD_POS) {
+        return NOT_A_DICT_POS;
+    }
+    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
+    int pos = nodePos;
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+    if (!PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+        return NOT_A_DICT_POS;
+    }
+    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
+    if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+    }
+    if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
+        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(dictRoot, flags, &pos);
+    }
+    return pos;
 }
 
 int PatriciaTriePolicy::getBigramsPositionOfNode(
         const BinaryDictionaryInfo *const binaryDictionaryInfo,
         const int nodePos) const {
-    return BinaryFormat::getBigramListPositionForWordPosition(
-            binaryDictionaryInfo->getDictRoot(), nodePos);
+    if (nodePos == NOT_A_VALID_WORD_POS) {
+        return NOT_A_DICT_POS;
+    }
+    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
+    int pos = nodePos;
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+    if (!PatriciaTrieReadingUtils::hasBigrams(flags)) {
+        return NOT_A_DICT_POS;
+    }
+    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
+    if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+    }
+    if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
+        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(dictRoot, flags, &pos);
+    }
+    if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+        BinaryDictionaryTerminalAttributesReadingUtils::skipShortcuts(binaryDictionaryInfo, &pos);
+    }
+    return pos;
 }
 
-int PatriciaTriePolicy::createAndGetLeavingChildNode(const DicNode *const dicNode, int pos,
-        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+int PatriciaTriePolicy::createAndGetLeavingChildNode(const DicNode *const dicNode,
+        const int nodePos, const BinaryDictionaryInfo *const binaryDictionaryInfo,
         const NodeFilter *const childrenFilter, DicNodeVector *childDicNodes) const {
-    const int nextPos = pos;
-    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(
-            binaryDictionaryInfo->getDictRoot(), &pos);
-    const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags));
-    const bool isTerminal = (0 != (BinaryFormat::FLAG_IS_TERMINAL & flags));
-    const bool hasChildren = BinaryFormat::hasChildrenInFlags(flags);
-    const bool isBlacklistedOrNotAWord = BinaryFormat::hasBlacklistedOrNotAWordFlag(flags);
-
-    int codePoint = BinaryFormat::getCodePointAndForwardPointer(
-            binaryDictionaryInfo->getDictRoot(), &pos);
-    ASSERT(NOT_A_CODE_POINT != codePoint);
-    // TODO: optimize this
+    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
+    int pos = nodePos;
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
     int mergedNodeCodePoints[MAX_WORD_LENGTH];
-    uint16_t mergedNodeCodePointCount = 0;
-    mergedNodeCodePoints[mergedNodeCodePointCount++] = codePoint;
-
-    do {
-        const int nextCodePoint = hasMultipleChars
-                ? BinaryFormat::getCodePointAndForwardPointer(
-                        binaryDictionaryInfo->getDictRoot(), &pos) : NOT_A_CODE_POINT;
-        const bool isLastChar = (NOT_A_CODE_POINT == nextCodePoint);
-        if (!isLastChar) {
-            mergedNodeCodePoints[mergedNodeCodePointCount++] = nextCodePoint;
-        }
-        codePoint = nextCodePoint;
-    } while (NOT_A_CODE_POINT != codePoint);
-
-    const int probability = isTerminal ? BinaryFormat::readProbabilityWithoutMovingPointer(
-            binaryDictionaryInfo->getDictRoot(), pos) : NOT_A_PROBABILITY;
-    pos = BinaryFormat::skipProbability(flags, pos);
-    int childrenPos = hasChildren ? BinaryFormat::readChildrenPosition(
-            binaryDictionaryInfo->getDictRoot(), flags, pos) : NOT_A_DICT_POS;
-    const int siblingPos = BinaryFormat::skipChildrenPosAndAttributes(
-            binaryDictionaryInfo->getDictRoot(), flags, pos);
-
-    if (childrenFilter->isFilteredOut(mergedNodeCodePoints[0])) {
-        return siblingPos;
+    const int mergedNodeCodePointCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
+            dictRoot, flags, MAX_WORD_LENGTH, mergedNodeCodePoints, &pos);
+    const int probability = (PatriciaTrieReadingUtils::isTerminal(flags))?
+            PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos)
+                    : NOT_A_PROBABILITY;
+    const int childrenPos = PatriciaTrieReadingUtils::hasChildrenInFlags(flags) ?
+            PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+                    dictRoot, flags, &pos) : NOT_A_DICT_POS;
+    if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+        BinaryDictionaryTerminalAttributesReadingUtils::skipShortcuts(binaryDictionaryInfo, &pos);
     }
-    childDicNodes->pushLeavingChild(dicNode, nextPos, childrenPos, probability, isTerminal,
-            hasChildren, isBlacklistedOrNotAWord, mergedNodeCodePointCount, mergedNodeCodePoints);
-    return siblingPos;
+    if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
+        BinaryDictionaryTerminalAttributesReadingUtils::skipExistingBigrams(
+                binaryDictionaryInfo, &pos);
+    }
+    if (!childrenFilter->isFilteredOut(mergedNodeCodePoints[0])) {
+        childDicNodes->pushLeavingChild(dicNode, nodePos, childrenPos, probability,
+                PatriciaTrieReadingUtils::isTerminal(flags),
+                PatriciaTrieReadingUtils::hasChildrenInFlags(flags),
+                PatriciaTrieReadingUtils::isBlacklisted(flags) ||
+                        PatriciaTrieReadingUtils::isNotAWord(flags),
+                mergedNodeCodePointCount, mergedNodeCodePoints);
+    }
+    return pos;
 }
 
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
index 42827d9..71f256e 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
@@ -61,7 +61,7 @@
     PatriciaTriePolicy() {}
     ~PatriciaTriePolicy() {}
 
-    int createAndGetLeavingChildNode(const DicNode *const dicNode, int pos,
+    int createAndGetLeavingChildNode(const DicNode *const dicNode, const int nodePos,
             const BinaryDictionaryInfo *const binaryDictionaryInfo,
             const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const;
 };
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
new file mode 100644
index 0000000..89e981d
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+typedef PatriciaTrieReadingUtils PtReadingUtils;
+
+const PtReadingUtils::NodeFlags PtReadingUtils::MASK_GROUP_ADDRESS_TYPE = 0xC0;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
+
+// Flag for single/multiple char group
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_HAS_MULTIPLE_CHARS = 0x20;
+// Flag for terminal groups
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_TERMINAL = 0x10;
+// Flag for shortcut targets presence
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_HAS_SHORTCUT_TARGETS = 0x08;
+// Flag for bigram presence
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_HAS_BIGRAMS = 0x04;
+// Flag for non-words (typically, shortcut only entries)
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_NOT_A_WORD = 0x02;
+// Flag for blacklist
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_BLACKLISTED = 0x01;
+
+/* static */ int PtReadingUtils::readChildrenPositionAndAdvancePosition(
+        const uint8_t *const buffer, const NodeFlags flags, int *const pos) {
+    const int base = *pos;
+    int offset = 0;
+    switch (MASK_GROUP_ADDRESS_TYPE & flags) {
+        case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
+            offset = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
+            offset = ByteArrayUtils::readUint16AndAdvancePosition(buffer, pos);
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
+            offset = ByteArrayUtils::readUint24AndAdvancePosition(buffer, pos);
+            break;
+        default:
+            // If we come here, it means we asked for the children of a word with
+            // no children.
+            return NOT_A_DICT_POS;
+    }
+    return base + offset;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
new file mode 100644
index 0000000..002c3f1
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013, 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_PATRICIA_TRIE_READING_UTILS_H
+#define LATINIME_PATRICIA_TRIE_READING_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+class PatriciaTrieReadingUtils {
+ public:
+    typedef uint8_t NodeFlags;
+
+    static AK_FORCE_INLINE int getGroupCountAndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint8_t firstByte = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+        if (firstByte < 0x80) {
+            return firstByte;
+        } else {
+            return ((firstByte & 0x7F) << 8) ^ ByteArrayUtils::readUint8AndAdvancePosition(
+                    buffer, pos);
+        }
+    }
+
+    static AK_FORCE_INLINE NodeFlags getFlagsAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos) {
+        return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+    }
+
+    static AK_FORCE_INLINE int getCodePointAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos) {
+        return ByteArrayUtils::readCodePointAndAdvancePosition(buffer, pos);
+    }
+
+    // Returns the number of read characters.
+    static AK_FORCE_INLINE int getCharsAndAdvancePosition(const uint8_t *const buffer,
+            const NodeFlags flags, const int maxLength, int *const outBuffer, int *const pos) {
+        int length = 0;
+        if (hasMultipleChars(flags)) {
+            length = ByteArrayUtils::readStringAndAdvancePosition(buffer, maxLength, outBuffer,
+                    pos);
+        } else {
+            if (maxLength > 0) {
+                outBuffer[0] = getCodePointAndAdvancePosition(buffer, pos);
+                length = 1;
+            }
+        }
+        return length;
+    }
+
+    // Returns the number of skipped characters.
+    static AK_FORCE_INLINE int skipCharacters(const uint8_t *const buffer, const NodeFlags flags,
+            const int maxLength, int *const pos) {
+        if (hasMultipleChars(flags)) {
+            return ByteArrayUtils::advancePositionToBehindString(buffer, maxLength, pos);
+        } else {
+            if (maxLength > 0) {
+                getCodePointAndAdvancePosition(buffer, pos);
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    static AK_FORCE_INLINE int readProbabilityAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos) {
+        return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+    }
+
+    static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer,
+            const NodeFlags flags, int *const pos);
+
+    /**
+     * Node Flags
+     */
+    static AK_FORCE_INLINE bool isBlacklisted(const NodeFlags flags) {
+        return (flags & FLAG_IS_BLACKLISTED) != 0;
+    }
+
+    static AK_FORCE_INLINE bool isNotAWord(const NodeFlags flags) {
+        return (flags & FLAG_IS_NOT_A_WORD) != 0;
+    }
+
+    static AK_FORCE_INLINE bool isTerminal(const NodeFlags flags) {
+        return (flags & FLAG_IS_TERMINAL) != 0;
+    }
+
+    static AK_FORCE_INLINE bool hasShortcutTargets(const NodeFlags flags) {
+        return (flags & FLAG_HAS_SHORTCUT_TARGETS) != 0;
+    }
+
+    static AK_FORCE_INLINE bool hasBigrams(const NodeFlags flags) {
+        return (flags & FLAG_HAS_BIGRAMS) != 0;
+    }
+
+    static AK_FORCE_INLINE bool hasMultipleChars(const NodeFlags flags) {
+        return (flags & FLAG_HAS_MULTIPLE_CHARS) != 0;
+    }
+
+    static AK_FORCE_INLINE bool hasChildrenInFlags(const NodeFlags flags) {
+        return FLAG_GROUP_ADDRESS_TYPE_NOADDRESS != (MASK_GROUP_ADDRESS_TYPE & flags);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(PatriciaTrieReadingUtils);
+
+    static const NodeFlags MASK_GROUP_ADDRESS_TYPE;
+    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_NOADDRESS;
+    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
+    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
+    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
+
+    static const NodeFlags FLAG_HAS_MULTIPLE_CHARS;
+    static const NodeFlags FLAG_IS_TERMINAL;
+    static const NodeFlags FLAG_HAS_SHORTCUT_TARGETS;
+    static const NodeFlags FLAG_HAS_BIGRAMS;
+    static const NodeFlags FLAG_IS_NOT_A_WORD;
+    static const NodeFlags FLAG_IS_BLACKLISTED;
+};
+} // namespace latinime
+#endif /* LATINIME_PATRICIA_TRIE_NODE_READING_UTILS_H */