initial commit

contains a copy of the native AOSP suggestion code as well as wrapper to make it compile
on chromeos, a wrapper for use as a native C++ library and a demo program.

BUG=chromium:329015
TEST=manual testing with the demo program
Change-Id: Iece71fbe862b7296dbd4f60a30964e8986935a72
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1a13fe5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.AppleDouble
+.DS_Store
+*.d
+*.o
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..28fc25f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,97 @@
+
+DESTDIR = /usr
+LIBDIR = $(DESTDIR)/lib
+SHAREDIR = $(DESTDIR)/share/libsuggest
+
+AOSP_SOURCE_FILES=src/aosp/compability/log_utils_mock.cpp\
+	src/aosp/suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp\
+	src/aosp/suggest/policyimpl/typing/scoring_params.cpp\
+	src/aosp/suggest/policyimpl/typing/typing_weighting.cpp\
+	src/aosp/suggest/policyimpl/typing/typing_suggest_policy.cpp\
+	src/aosp/suggest/policyimpl/typing/typing_traversal.cpp\
+	src/aosp/suggest/policyimpl/typing/typing_scoring.cpp\
+	src/aosp/suggest/policyimpl/dictionary/header/header_read_write_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/header/header_policy.cpp\
+	src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp\
+	src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp\
+	src/aosp/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/patricia_trie_policy.cpp\
+	src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp\
+	src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp\
+	src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp\
+	src/aosp/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp\
+	src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp\
+	src/aosp/suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.cpp\
+	src/aosp/suggest/policyimpl/dictionary/utils/format_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/utils/dict_file_writing_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/utils/forgetting_curve_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/utils/byte_array_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/shortcut/shortcut_list_reading_utils.cpp\
+	src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp\
+	src/aosp/suggest/core/session/dic_traverse_session.cpp\
+	src/aosp/suggest/core/dicnode/dic_node_utils.cpp\
+	src/aosp/suggest/core/dicnode/dic_nodes_cache.cpp\
+	src/aosp/suggest/core/dicnode/dic_node.cpp\
+	src/aosp/suggest/core/suggest.cpp\
+	src/aosp/suggest/core/policy/weighting.cpp\
+	src/aosp/suggest/core/layout/proximity_info_state.cpp\
+	src/aosp/suggest/core/layout/proximity_info.cpp\
+	src/aosp/suggest/core/layout/proximity_info_state_utils.cpp\
+	src/aosp/suggest/core/layout/additional_proximity_chars.cpp\
+	src/aosp/suggest/core/layout/proximity_info_params.cpp\
+	src/aosp/suggest/core/dictionary/bigram_dictionary.cpp\
+	src/aosp/suggest/core/dictionary/multi_bigram_map.cpp\
+	src/aosp/suggest/core/dictionary/digraph_utils.cpp\
+	src/aosp/suggest/core/dictionary/bloom_filter.cpp\
+	src/aosp/suggest/core/dictionary/dictionary.cpp\
+	src/aosp/utils/char_utils.cpp\
+	src/aosp/utils/autocorrection_threshold_utils.cpp
+
+SUGGEST_SOURCE_FILES=src/suggest.cpp
+
+SUGGEST_DEMO_FILES=src/demo.cpp
+
+CXXFLAGS=-Iinclude -Isrc/aosp -Isrc/aosp/compability -Isrc -std=c++11
+
+
+# libsuggest.so library
+
+LIB_SOURCES=$(SUGGEST_SOURCE_FILES) $(AOSP_SOURCE_FILES)
+LIB_OBJECTS=$(LIB_SOURCES:%.cpp=%.pic.o)
+
+libsuggest.so: $(LIB_OBJECTS)
+	g++ -shared -o libsuggest.so $(CXXFLAGS) $(LIB_OBJECTS)
+
+%.pic.o: %.cpp
+	g++ $(CXXFLAGS) -fPIC -MD -g -c -o $@ $<
+
+
+# suggest_demo executable
+
+DEMO_SOURCES=$(SUGGEST_DEMO_FILES) $(LIB_SOURCES)
+DEMO_OBJECTS=$(DEMO_SOURCES:%.cpp=%.pie.o)
+
+suggest_demo: libsuggest.so $(DEMO_OBJECTS)
+	g++ -o suggest_demo -lsuggest -lncurses -L. $(CXXFLAGS) $(DEMO_OBJECTS)
+
+%.pie.o: %.cpp
+	g++ $(CXXFLAGS) -MD -g -c -o $@ $<
+
+# common rules
+
+ALL_OBJECTS=$(DEMO_OBJECTS) $(LIB_OBJECTS)
+
+clean:
+	rm libsuggest.so | true
+	rm suggest_demo | true
+	rm $(ALL_OBJECTS) | true
+
+install: libsuggest.so
+	install -D -m 0755 libsuggest.so $(LIBDIR)/libsuggest.so
+	install -D -m 0755 dicts/en_US.dict $(SHAREDIR)/en_US.dict
+
+all: libsuggest.so suggest_demo
+
+-include $(ALL_OBJECTS:%.o=%.d)
\ No newline at end of file
diff --git a/dicts/en_US.dict b/dicts/en_US.dict
new file mode 100644
index 0000000..09b6992
--- /dev/null
+++ b/dicts/en_US.dict
Binary files differ
diff --git a/include/suggest/suggest.h b/include/suggest/suggest.h
new file mode 100644
index 0000000..500bb3d
--- /dev/null
+++ b/include/suggest/suggest.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef SUGGEST_SUGGEST_H_
+#define SUGGEST_SUGGEST_H_
+
+#include <string>
+#include <list>
+#include <vector>
+
+#include "types.h"
+
+namespace suggest {
+
+class SuggestEngine;
+class SuggestEngineSession;
+
+// Description of a key.
+// The coordinate system can be chosen by the user of this library,
+// but keep in mind that all calculations are made in integer arithmetics.
+// the rectangle is defined as location of the top left corner and size.
+struct Key {
+	Key() {}
+	Key(vec2f location, vec2f size, charcode _code) :
+		rect(location, size), code(_code) {
+	}
+
+	rect2f rect;
+	charcode code;
+
+	static Key InvalidKey;
+};
+
+// A single word suggestion including confidences
+struct Suggestion {
+	std::string word;
+	int frequency;
+	int commit_first_word_confidence;
+};
+
+// Next to the coordinates of a touch, suggest needs to know which
+// character your keyboard recognized and displays to the users.
+struct Touch {
+	Touch() {}
+	Touch(vec2f pos, charcode code);
+
+	// create touch based on location only. Looks up which key is
+	// at this location.
+	Touch(vec2f pos, const SuggestEngine& engine);
+
+	// create a touch based on char code, assumes the key is hit right at
+	// the center.
+	Touch(charcode code, const SuggestEngine& engine);
+
+	vec2f pos;
+	charcode code;
+};
+
+// Parameters to tweak the suggestion process.
+// The list of parameters might change during iterations of the
+// library, but the constructor will always choose sane default values.
+// The provided locale name will be used to pick the right dictionary
+// from /usr/share/libsuggest/
+struct SuggestParameters {
+	SuggestParameters(std::string locale);
+
+	vec2f grid_cells;
+	float search_box_size_factor;
+	std::string locale;
+};
+
+// I am not 100% sure what the session concept in AOSP is used for,
+// so far I have been using a single session for all suggestions.
+// The session allows you to receive suggestions based on a list
+// of touch coordinates and (optional) the previously typed word.
+class SuggestSession {
+  public:
+	virtual const std::list<Suggestion>& GetSuggestions(
+			const std::vector<Touch> &touches,
+			std::string previous_word="") = 0;
+};
+
+// Main class of the suggestion process, which builds the keyboard definition
+// and allows suggest sessions to be created with this keyboard.
+class SuggestEngine {
+  public:
+  	virtual Key GetKeyAt(vec2f pos) const = 0;
+  	virtual Key GetKey(charcode code) const = 0;
+
+  	// start a new session
+  	virtual bool LoadDictionary(std::string locale) = 0;
+  	virtual SuggestSession* NewSession() = 0;
+};
+
+// To hide the implementation details of the SuggestEngine class
+// (especially for hiding the AOSP API)
+SuggestEngine* NewSuggestEngine(vec2f keyboard_size, vec2f common_key_size,
+						        const std::vector<Key> &keylist,
+						        const SuggestParameters &parameters);
+
+
+}  // namespace suggest
+
+#endif  // SUGGEST_SUGGEST_H_
\ No newline at end of file
diff --git a/include/suggest/types.h b/include/suggest/types.h
new file mode 100644
index 0000000..6c7c77e
--- /dev/null
+++ b/include/suggest/types.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef SUGGEST_TYPES_H_
+#define SUGGEST_TYPES_H_
+
+#include <cmath>
+
+namespace suggest {
+
+// Simple 2D math vector for locations and sizes
+template<typename T> struct vec2 {
+	T x;
+	T y;
+
+	vec2() {}
+	vec2(T _x, T _y) : x(_x), y(_y) {}
+
+	T abs_sq() {
+		return x*x + y*y;
+	}
+	T abs() {
+		return (T)sqrt(static_cast<double>(abs_sq()));
+	}
+};
+
+template<typename T> vec2<T> add(vec2<T> a, vec2<T> b) {
+	vec2<T> res;
+	res.x = a.x + b.x;
+	res.y = a.y + b.y;
+	return res;
+}
+
+template<typename T> vec2<T> sub(vec2<T> a, vec2<T> b) {
+	vec2<T> res;
+	res.x = a.x - b.x;
+	res.y = a.y - b.y;
+	return res;
+}
+
+template<typename T> vec2<T> mul_elem(vec2<T> a, vec2<T> b) {
+	vec2<T> res;
+	res.x = a.x * b.x;
+	res.y = a.y * b.y;
+	return res;
+}
+
+template<typename T> vec2<T> div_elem(vec2<T> a, vec2<T> b) {
+	vec2<T> res;
+	res.x = a.x / b.x;
+	res.y = a.y / b.y;
+	return res;
+}
+
+template<typename T> vec2<T> mul(vec2<T> a, T b) {
+	vec2<T> res;
+	res.x = a.x * b;
+	res.y = a.y * b;
+	return res;
+}
+
+template<typename T> vec2<T> div(vec2<T> a, T b) {
+	vec2<T> res;
+	res.x = a.x / b;
+	res.y = a.y / b;
+	return res;
+}
+
+typedef vec2<int> vec2i;
+typedef vec2<float> vec2f;
+
+template<typename T> struct rect2 {
+	vec2<T> pos;
+	vec2<T> size;
+
+	rect2() {}
+	rect2(vec2<T> pos, vec2<T> size) : pos(pos), size(size) {}
+
+	vec2<T> center() const {
+		return add(pos, mul(size, 0.5f));
+	}
+
+	rect2<T> resized(vec2<T> new_size) const {
+		return rect2<T>(sub(center(), mul(new_size, 0.5f)), new_size);
+	}
+
+	bool intersects(rect2 other) const {
+		vec2<T> delta = sub(other.pos, pos);
+		return delta.x < size.x && delta.x > -other.size.x &&
+			   delta.y < size.y && delta.y > -other.size.y;
+	}
+
+	bool contains(vec2<T> point) const {
+		return point.x > pos.x && point.x < pos.x + size.x &&
+		       point.x > pos.y && point.y < pos.y + size.y;
+	}
+};
+
+typedef rect2<int> rect2i;
+typedef rect2<float> rect2f;
+
+typedef int charcode;
+
+}  // namespace suggest
+
+#endif  // SUGGEST_TYPES_H_
\ No newline at end of file
diff --git a/keylist.txt b/keylist.txt
new file mode 100644
index 0000000..43bf9c1
--- /dev/null
+++ b/keylist.txt
@@ -0,0 +1,31 @@
+400 700
+100 100
+28
+a     0   0 100 100
+b   100   0 100 100
+c   200   0 100 100
+d   300   0 100 100
+e     0 100 100 100
+f   100 100 100 100
+g   200 100 100 100
+h   300 100 100 100
+i     0 200 100 100
+j   100 200 100 100
+k   200 200 100 100
+l   300 200 100 100
+m     0 300 100 100
+n   100 300 100 100
+o   200 300 100 100
+p   300 300 100 100
+q     0 400 100 100
+r   100 400 100 100
+s   200 400 100 100
+t   300 400 100 100
+u     0 500 100 100
+v   100 500 100 100
+w   200 500 100 100
+x   300 500 100 100
+y     0 600 100 100
+z   100 600 100 100
+.   200 600 100 100
+SPC 300 600 100 100
diff --git a/src/aosp/compability/android/log.h b/src/aosp/compability/android/log.h
new file mode 100644
index 0000000..04fabc6
--- /dev/null
+++ b/src/aosp/compability/android/log.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef AOSP_COMPABILITY_ANDROID_LOG_H_
+#define AOSP_COMPABILITY_ANDROID_LOG_H_
+
+namespace latinime {
+	class LogUtils {
+		void logToJava(JNIEnv *env, char const *fmt, ...) {
+		    va_list args;
+		    va_start(args, fmt);
+		    vprintf(fmt, args);
+		    va_end(args);
+		}
+	};
+};
+
+#endif
diff --git a/src/aosp/compability/jni.h b/src/aosp/compability/jni.h
new file mode 100644
index 0000000..3be988b
--- /dev/null
+++ b/src/aosp/compability/jni.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef AOSP_COMPABILITY_JNI_H_
+#define AOSP_COMPABILITY_JNI_H_
+
+#include <vector>
+#include <string>
+
+typedef std::string jstring;
+typedef std::vector<int> *jintArray;
+typedef std::vector<float> *jfloatArray;
+typedef int jint;
+typedef long int jlong;
+typedef float jfloat;
+typedef size_t jsize;
+
+class JNIEnv {
+ public:
+   void GetIntArrayRegion(jintArray array, jsize offset, jsize len, jint* buffer) {
+      if (offset + len > array->size())
+         return;
+      std::copy(array->begin() + offset, array->begin() + offset + len, buffer);
+   }
+   void GetFloatArrayRegion(jfloatArray array, jsize offset, jsize len, jfloat* buffer) {
+      if (offset + len > array->size())
+         return;
+      std::copy(array->begin() + offset, array->begin() + offset + len, buffer);
+   }
+   jsize GetArrayLength(jintArray array) {
+      return array->size();
+   }
+   jsize GetArrayLength(jfloatArray array) {
+      return array->size();
+   }
+   void GetStringUTFRegion(jstring str, jsize offset, jsize len, char* buffer) {
+      if (offset + len > str.size())
+         return;
+      std::copy(str.begin() + offset, str.begin() + offset + len, buffer);
+   }
+   jsize GetStringUTFLength(jstring string) {
+      return string.size();
+   }
+   jsize GetStringLength(jstring string) {
+      return string.size();
+   }
+};
+
+#endif
diff --git a/src/aosp/compability/log_utils_mock.cpp b/src/aosp/compability/log_utils_mock.cpp
new file mode 100644
index 0000000..1496cc8
--- /dev/null
+++ b/src/aosp/compability/log_utils_mock.cpp
@@ -0,0 +1,19 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utils/log_utils.h>
+
+#include <cstdio>
+#include <stdarg.h>
+
+#include "defines.h"
+
+namespace latinime {
+    /* static */ void LogUtils::logToJava(JNIEnv *const env, const char *const format, ...) {
+        va_list args;
+        va_start(args, format);
+        vprintf(format, args);
+        va_end(args);
+    }
+}
diff --git a/src/aosp/defines.h b/src/aosp/defines.h
new file mode 100644
index 0000000..742e388
--- /dev/null
+++ b/src/aosp/defines.h
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2010 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_DEFINES_H
+#define LATINIME_DEFINES_H
+
+#ifdef __GNUC__
+#define AK_FORCE_INLINE __attribute__((always_inline)) __inline__
+#else // __GNUC__
+#define AK_FORCE_INLINE inline
+#endif // __GNUC__
+
+#if defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
+#undef AK_FORCE_INLINE
+#define AK_FORCE_INLINE inline
+#endif // defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
+
+// Must be equal to Constants.Dictionary.MAX_WORD_LENGTH in Java
+#define MAX_WORD_LENGTH 48
+// Must be equal to BinaryDictionary.MAX_RESULTS in Java
+#define MAX_RESULTS 18
+// Must be equal to ProximityInfo.MAX_PROXIMITY_CHARS_SIZE in Java
+#define MAX_PROXIMITY_CHARS_SIZE 16
+#define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
+#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
+
+AK_FORCE_INLINE static int intArrayToCharArray(const int *const source, const int sourceSize,
+        char *dest, const int destSize) {
+    // We want to always terminate with a 0 char, so stop one short of the length to make
+    // sure there is room.
+    const int destLimit = destSize - 1;
+    int si = 0;
+    int di = 0;
+    while (si < sourceSize && di < destLimit && 0 != source[si]) {
+        const int codePoint = source[si++];
+        if (codePoint < 0x7F) { // One byte
+            dest[di++] = codePoint;
+        } else if (codePoint < 0x7FF) { // Two bytes
+            if (di + 1 >= destLimit) break;
+            dest[di++] = 0xC0 + (codePoint >> 6);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint < 0xFFFF) { // Three bytes
+            if (di + 2 >= destLimit) break;
+            dest[di++] = 0xE0 + (codePoint >> 12);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint <= 0x1FFFFF) { // Four bytes
+            if (di + 3 >= destLimit) break;
+            dest[di++] = 0xF0 + (codePoint >> 18);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint <= 0x3FFFFFF) { // Five bytes
+            if (di + 4 >= destLimit) break;
+            dest[di++] = 0xF8 + (codePoint >> 24);
+            dest[di++] = 0x80 + ((codePoint >> 18) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = codePoint & 0x3F;
+        } else if (codePoint <= 0x7FFFFFFF) { // Six bytes
+            if (di + 5 >= destLimit) break;
+            dest[di++] = 0xFC + (codePoint >> 30);
+            dest[di++] = 0x80 + ((codePoint >> 24) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 18) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = codePoint & 0x3F;
+        } else {
+            // Not a code point... skip.
+        }
+    }
+    dest[di] = 0;
+    return di;
+}
+
+#if defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
+#include <android/log.h>
+#ifndef LOG_TAG
+#define LOG_TAG "LatinIME: "
+#endif // LOG_TAG
+#define AKLOGE(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##__VA_ARGS__)
+#define AKLOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##__VA_ARGS__)
+
+#define DUMP_RESULT(words, frequencies) do { dumpResult(words, frequencies); } while (0)
+#define DUMP_WORD(word, length) do { dumpWord(word, length); } while (0)
+#define INTS_TO_CHARS(input, length, output, outlength) do { \
+        intArrayToCharArray(input, length, output, outlength); } while (0)
+
+static inline void dumpWordInfo(const int *word, const int length, const int rank,
+        const int probability) {
+    static char charBuf[50];
+    const int N = intArrayToCharArray(word, length, charBuf, NELEMS(charBuf));
+    if (N > 1) {
+        AKLOGI("%2d [ %s ] (%d)", rank, charBuf, probability);
+    }
+}
+
+static inline void dumpResult(const int *outWords, const int *frequencies) {
+    AKLOGI("--- DUMP RESULT ---------");
+    for (int i = 0; i < MAX_RESULTS; ++i) {
+        dumpWordInfo(&outWords[i * MAX_WORD_LENGTH], MAX_WORD_LENGTH, i, frequencies[i]);
+    }
+    AKLOGI("-------------------------");
+}
+
+static AK_FORCE_INLINE void dumpWord(const int *word, const int length) {
+    static char charBuf[50];
+    const int N = intArrayToCharArray(word, length, charBuf, NELEMS(charBuf));
+    if (N > 1) {
+        AKLOGI("[ %s ]", charBuf);
+    }
+}
+
+#ifndef __ANDROID__
+#include <cassert>
+#include <execinfo.h>
+#include <stdlib.h>
+
+#define DO_ASSERT_TEST
+#define ASSERT(success) do { if (!(success)) { showStackTrace(); assert(success);} } while (0)
+#define SHOW_STACK_TRACE do { showStackTrace(); } while (0)
+
+static inline void showStackTrace() {
+    void *callstack[128];
+    int i, frames = backtrace(callstack, 128);
+    char **strs = backtrace_symbols(callstack, frames);
+    for (i = 0; i < frames; ++i) {
+        if (i == 0) {
+            AKLOGI("=== Trace ===");
+            continue;
+        }
+        AKLOGI("%s", strs[i]);
+    }
+    free(strs);
+}
+#else // __ANDROID__
+#include <cassert>
+#define DO_ASSERT_TEST
+#define ASSERT(success) assert(success)
+#define SHOW_STACK_TRACE
+#endif // __ANDROID__
+
+#else // defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
+#define AKLOGE(fmt, ...)
+#define AKLOGI(fmt, ...)
+#define DUMP_RESULT(words, frequencies)
+#define DUMP_WORD(word, length)
+#undef DO_ASSERT_TEST
+#define ASSERT(success)
+#define SHOW_STACK_TRACE
+#define INTS_TO_CHARS(input, length, output)
+#endif // defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
+
+#ifdef FLAG_DO_PROFILE
+// Profiler
+#include <time.h>
+
+#define PROF_BUF_SIZE 100
+static float profile_buf[PROF_BUF_SIZE];
+static float profile_old[PROF_BUF_SIZE];
+static unsigned int profile_counter[PROF_BUF_SIZE];
+
+#define PROF_RESET               prof_reset()
+#define PROF_COUNT(prof_buf_id)  ++profile_counter[prof_buf_id]
+#define PROF_OPEN                do { PROF_RESET; PROF_START(PROF_BUF_SIZE - 1); } while (0)
+#define PROF_START(prof_buf_id)  do { \
+        PROF_COUNT(prof_buf_id); profile_old[prof_buf_id] = (clock()); } while (0)
+#define PROF_CLOSE               do { PROF_END(PROF_BUF_SIZE - 1); PROF_OUTALL; } while (0)
+#define PROF_END(prof_buf_id)    profile_buf[prof_buf_id] += ((clock()) - profile_old[prof_buf_id])
+#define PROF_CLOCKOUT(prof_buf_id) \
+        AKLOGI("%s : clock is %f", __FUNCTION__, (clock() - profile_old[prof_buf_id]))
+#define PROF_OUTALL              do { AKLOGI("--- %s ---", __FUNCTION__); prof_out(); } while (0)
+
+static inline void prof_reset(void) {
+    for (int i = 0; i < PROF_BUF_SIZE; ++i) {
+        profile_buf[i] = 0;
+        profile_old[i] = 0;
+        profile_counter[i] = 0;
+    }
+}
+
+static inline void prof_out(void) {
+    if (profile_counter[PROF_BUF_SIZE - 1] != 1) {
+        AKLOGI("Error: You must call PROF_OPEN before PROF_CLOSE.");
+    }
+    AKLOGI("Total time is %6.3f ms.",
+            profile_buf[PROF_BUF_SIZE - 1] * 1000.0f / static_cast<float>(CLOCKS_PER_SEC));
+    float all = 0.0f;
+    for (int i = 0; i < PROF_BUF_SIZE - 1; ++i) {
+        all += profile_buf[i];
+    }
+    if (all < 1.0f) all = 1.0f;
+    for (int i = 0; i < PROF_BUF_SIZE - 1; ++i) {
+        if (profile_buf[i] > 0.0f) {
+            AKLOGI("(%d): Used %4.2f%%, %8.4f ms. Called %d times.",
+                    i, (profile_buf[i] * 100.0f / all),
+                    profile_buf[i] * 1000.0f / static_cast<float>(CLOCKS_PER_SEC),
+                    profile_counter[i]);
+        }
+    }
+}
+
+#else // FLAG_DO_PROFILE
+#define PROF_BUF_SIZE 0
+#define PROF_RESET
+#define PROF_COUNT(prof_buf_id)
+#define PROF_OPEN
+#define PROF_START(prof_buf_id)
+#define PROF_CLOSE
+#define PROF_END(prof_buf_id)
+#define PROF_CLOCK_OUT(prof_buf_id)
+#define PROF_CLOCKOUT(prof_buf_id)
+#define PROF_OUTALL
+
+#endif // FLAG_DO_PROFILE
+
+#ifdef FLAG_DBG
+#define DEBUG_DICT true
+#define DEBUG_DICT_FULL false
+#define DEBUG_EDIT_DISTANCE false
+#define DEBUG_NODE DEBUG_DICT_FULL
+#define DEBUG_TRACE DEBUG_DICT_FULL
+#define DEBUG_PROXIMITY_INFO false
+#define DEBUG_PROXIMITY_CHARS false
+#define DEBUG_CORRECTION false
+#define DEBUG_CORRECTION_FREQ false
+#define DEBUG_SAMPLING_POINTS false
+#define DEBUG_POINTS_PROBABILITY false
+#define DEBUG_DOUBLE_LETTER false
+#define DEBUG_CACHE false
+#define DEBUG_DUMP_ERROR false
+#define DEBUG_EVALUATE_MOST_PROBABLE_STRING false
+
+#ifdef FLAG_FULL_DBG
+#define DEBUG_GEO_FULL true
+#else
+#define DEBUG_GEO_FULL false
+#endif
+
+#else // FLAG_DBG
+
+#define DEBUG_DICT false
+#define DEBUG_DICT_FULL false
+#define DEBUG_EDIT_DISTANCE false
+#define DEBUG_NODE false
+#define DEBUG_TRACE false
+#define DEBUG_PROXIMITY_INFO false
+#define DEBUG_PROXIMITY_CHARS false
+#define DEBUG_CORRECTION false
+#define DEBUG_CORRECTION_FREQ false
+#define DEBUG_SAMPLING_POINTS false
+#define DEBUG_POINTS_PROBABILITY false
+#define DEBUG_DOUBLE_LETTER false
+#define DEBUG_CACHE false
+#define DEBUG_DUMP_ERROR false
+#define DEBUG_EVALUATE_MOST_PROBABLE_STRING false
+
+#define DEBUG_GEO_FULL false
+
+#endif // FLAG_DBG
+
+#ifndef S_INT_MAX
+#define S_INT_MAX 2147483647 // ((1 << 31) - 1)
+#endif
+#ifndef S_INT_MIN
+// The literal constant -2147483648 does not work in C prior C90, because
+// the compiler tries to fit the positive number into an int and then negate it.
+// GCC warns about this.
+#define S_INT_MIN (-2147483647 - 1) // -(1 << 31)
+#endif
+
+#define M_PI_F 3.14159265f
+#define MAX_PERCENTILE 100
+
+// Number of base-10 digits in the largest integer + 1 to leave room for a zero terminator.
+// As such, this is the maximum number of characters will be needed to represent an int as a
+// string, including the terminator; this is used as the size of a string buffer large enough to
+// hold any value that is intended to fit in an integer, e.g. in the code that reads the header
+// of the binary dictionary where a {key,value} string pair scheme is used.
+#define LARGEST_INT_DIGIT_COUNT 11
+
+#define NOT_A_CODE_POINT (-1)
+#define NOT_A_DISTANCE (-1)
+#define NOT_A_COORDINATE (-1)
+#define NOT_AN_INDEX (-1)
+#define NOT_A_PROBABILITY (-1)
+#define NOT_A_DICT_POS (S_INT_MIN)
+
+// A special value to mean the first word confidence makes no sense in this case,
+// e.g. this is not a multi-word suggestion.
+#define NOT_A_FIRST_WORD_CONFIDENCE (S_INT_MAX)
+// How high the confidence needs to be for us to auto-commit. Arbitrary.
+// This needs to be the same as CONFIDENCE_FOR_AUTO_COMMIT in BinaryDictionary.java
+#define CONFIDENCE_FOR_AUTO_COMMIT (1000000)
+// 80% of the full confidence
+#define DISTANCE_WEIGHT_FOR_AUTO_COMMIT (80 * CONFIDENCE_FOR_AUTO_COMMIT / 100)
+// 100% of the full confidence
+#define LENGTH_WEIGHT_FOR_AUTO_COMMIT (CONFIDENCE_FOR_AUTO_COMMIT)
+// 80% of the full confidence
+#define SPACE_COUNT_WEIGHT_FOR_AUTO_COMMIT (80 * CONFIDENCE_FOR_AUTO_COMMIT / 100)
+
+#define KEYCODE_SPACE ' '
+#define KEYCODE_SINGLE_QUOTE '\''
+#define KEYCODE_HYPHEN_MINUS '-'
+
+#define SUGGEST_INTERFACE_OUTPUT_SCALE 1000000.0f
+#define MAX_PROBABILITY 255
+#define MAX_BIGRAM_ENCODED_PROBABILITY 15
+
+// Assuming locale strings such as en_US, sr-Latn etc.
+#define MAX_LOCALE_STRING_LENGTH 10
+
+// Max value for length, distance and probability which are used in weighting
+// TODO: Remove
+#define MAX_VALUE_FOR_WEIGHTING 10000000
+
+// The max number of the keys in one keyboard layout
+#define MAX_KEY_COUNT_IN_A_KEYBOARD 64
+
+// TODO: Remove
+#define MAX_POINTER_COUNT 1
+#define MAX_POINTER_COUNT_G 2
+
+template<typename T> AK_FORCE_INLINE const T &min(const T &a, const T &b) { return a < b ? a : b; }
+template<typename T> AK_FORCE_INLINE const T &max(const T &a, const T &b) { return a > b ? a : b; }
+
+// DEBUG
+#define INPUTLENGTH_FOR_DEBUG (-1)
+#define MIN_OUTPUT_INDEX_FOR_DEBUG (-1)
+
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+  TypeName(const TypeName&);               \
+  void operator=(const TypeName&)
+
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+  TypeName();                                    \
+  DISALLOW_COPY_AND_ASSIGN(TypeName)
+
+// Used as a return value for character comparison
+typedef enum {
+    // Same char, possibly with different case or accent
+    MATCH_CHAR,
+    // It is a char located nearby on the keyboard
+    PROXIMITY_CHAR,
+    // Additional proximity char which can differ by language.
+    ADDITIONAL_PROXIMITY_CHAR,
+    // It is a substitution char
+    SUBSTITUTION_CHAR,
+    // It is an unrelated char
+    UNRELATED_CHAR,
+} ProximityType;
+
+typedef enum {
+    NOT_A_DOUBLE_LETTER,
+    A_DOUBLE_LETTER,
+    A_STRONG_DOUBLE_LETTER
+} DoubleLetterLevel;
+
+typedef enum {
+    // Correction for MATCH_CHAR
+    CT_MATCH,
+    // Correction for PROXIMITY_CHAR
+    CT_PROXIMITY,
+    // Correction for ADDITIONAL_PROXIMITY_CHAR
+    CT_ADDITIONAL_PROXIMITY,
+    // Correction for SUBSTITUTION_CHAR
+    CT_SUBSTITUTION,
+    // Skip one omitted letter
+    CT_OMISSION,
+    // Delete an unnecessarily inserted letter
+    CT_INSERTION,
+    // Swap the order of next two touch points
+    CT_TRANSPOSITION,
+    CT_COMPLETION,
+    CT_TERMINAL,
+    CT_TERMINAL_INSERTION,
+    // Create new word with space omission
+    CT_NEW_WORD_SPACE_OMISSION,
+    // Create new word with space substitution
+    CT_NEW_WORD_SPACE_SUBSTITUTION,
+} CorrectionType;
+
+// ErrorType is mainly decided by CorrectionType but it is also depending on if
+// the correction has really been performed or not.
+typedef enum {
+    // Substitution, omission and transposition
+    ET_EDIT_CORRECTION,
+    // Proximity error
+    ET_PROXIMITY_CORRECTION,
+    // Completion
+    ET_COMPLETION,
+    // New word
+    // TODO: Remove.
+    // A new word error should be an edit correction error or a proximity correction error.
+    ET_NEW_WORD,
+    // Treat error as an intentional omission when the CorrectionType is omission and the node can
+    // be intentional omission.
+    ET_INTENTIONAL_OMISSION,
+    // Not treated as an error. Tracked for checking exact match
+    ET_NOT_AN_ERROR
+} ErrorType;
+#endif // LATINIME_DEFINES_H
diff --git a/src/aosp/suggest/core/dicnode/dic_node.cpp b/src/aosp/suggest/core/dicnode/dic_node.cpp
new file mode 100644
index 0000000..de088c7
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_node.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dicnode/dic_node.h"
+
+namespace latinime {
+
+DicNode::DicNode(const DicNode &dicNode)
+        :
+#if DEBUG_DICT
+          mProfiler(dicNode.mProfiler),
+#endif
+          mDicNodeProperties(dicNode.mDicNodeProperties), mDicNodeState(dicNode.mDicNodeState),
+          mIsCachedForNextSuggestion(dicNode.mIsCachedForNextSuggestion), mIsUsed(dicNode.mIsUsed),
+          mReleaseListener(0) {
+    /* empty */
+}
+
+DicNode &DicNode::operator=(const DicNode &dicNode) {
+#if DEBUG_DICT
+    mProfiler = dicNode.mProfiler;
+#endif
+    mDicNodeProperties = dicNode.mDicNodeProperties;
+    mDicNodeState = dicNode.mDicNodeState;
+    mIsCachedForNextSuggestion = dicNode.mIsCachedForNextSuggestion;
+    mIsUsed = dicNode.mIsUsed;
+    mReleaseListener = dicNode.mReleaseListener;
+    return *this;
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/core/dicnode/dic_node.h b/src/aosp/suggest/core/dicnode/dic_node.h
new file mode 100644
index 0000000..49cfdec
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_node.h
@@ -0,0 +1,617 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_H
+#define LATINIME_DIC_NODE_H
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node_profiler.h"
+#include "suggest/core/dicnode/dic_node_release_listener.h"
+#include "suggest/core/dicnode/internal/dic_node_state.h"
+#include "suggest/core/dicnode/internal/dic_node_properties.h"
+#include "suggest/core/dictionary/digraph_utils.h"
+#include "utils/char_utils.h"
+
+#if DEBUG_DICT
+#define LOGI_SHOW_ADD_COST_PROP \
+        do { char charBuf[50]; \
+        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
+        AKLOGI("%20s, \"%c\", size = %03d, total = %03d, index(0) = %02d, dist = %.4f, %s,,", \
+                __FUNCTION__, getNodeCodePoint(), inputSize, getTotalInputIndex(), \
+                getInputIndex(0), getNormalizedCompoundDistance(), charBuf); } while (0)
+#define DUMP_WORD_AND_SCORE(header) \
+        do { char charBuf[50]; char prevWordCharBuf[50]; \
+        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
+        INTS_TO_CHARS(mDicNodeState.mDicNodeStatePrevWord.mPrevWord, \
+                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf, \
+                NELEMS(prevWordCharBuf)); \
+        AKLOGI("#%8s, %5f, %5f, %5f, %5f, %s, %s, %d, %5f,", header, \
+                getSpatialDistanceForScoring(), getLanguageDistanceForScoring(), \
+                getNormalizedCompoundDistance(), getRawLength(), prevWordCharBuf, charBuf, \
+                getInputIndex(0), getNormalizedCompoundDistanceAfterFirstWord()); \
+        } while (0)
+#else
+#define LOGI_SHOW_ADD_COST_PROP
+#define DUMP_WORD_AND_SCORE(header)
+#endif
+
+namespace latinime {
+
+// This struct is purely a bucket to return values. No instances of this struct should be kept.
+struct DicNode_InputStateG {
+    DicNode_InputStateG()
+            : mNeedsToUpdateInputStateG(false), mPointerId(0), mInputIndex(0),
+              mPrevCodePoint(0), mTerminalDiffCost(0.0f), mRawLength(0.0f),
+              mDoubleLetterLevel(NOT_A_DOUBLE_LETTER) {}
+
+    bool mNeedsToUpdateInputStateG;
+    int mPointerId;
+    int16_t mInputIndex;
+    int mPrevCodePoint;
+    float mTerminalDiffCost;
+    float mRawLength;
+    DoubleLetterLevel mDoubleLetterLevel;
+};
+
+class DicNode {
+    // Caveat: We define Weighting as a friend class of DicNode to let Weighting change
+    // the distance of DicNode.
+    // Caution!!! In general, we avoid using the "friend" access modifier.
+    // This is an exception to explicitly hide DicNode::addCost() from all classes but Weighting.
+    friend class Weighting;
+
+ public:
+#if DEBUG_DICT
+    DicNodeProfiler mProfiler;
+#endif
+    //////////////////
+    // Memory utils //
+    //////////////////
+    AK_FORCE_INLINE static void managedDelete(DicNode *node) {
+        node->remove();
+    }
+    // end
+    /////////////////
+
+    AK_FORCE_INLINE DicNode()
+            :
+#if DEBUG_DICT
+              mProfiler(),
+#endif
+              mDicNodeProperties(), mDicNodeState(), mIsCachedForNextSuggestion(false),
+              mIsUsed(false), mReleaseListener(0) {}
+
+    DicNode(const DicNode &dicNode);
+    DicNode &operator=(const DicNode &dicNode);
+    virtual ~DicNode() {}
+
+    // Init for copy
+    void initByCopy(const DicNode *dicNode) {
+        mIsUsed = true;
+        mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
+        mDicNodeProperties.init(&dicNode->mDicNodeProperties);
+        mDicNodeState.init(&dicNode->mDicNodeState);
+        PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
+    }
+
+    // Init for root with prevWordNodePos which is used for bigram
+    void initAsRoot(const int rootGroupPos, const int prevWordNodePos) {
+        mIsUsed = true;
+        mIsCachedForNextSuggestion = false;
+        mDicNodeProperties.init(
+                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
+                true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */,
+                0 /* terminalDepth */);
+        mDicNodeState.init(prevWordNodePos);
+        PROF_NODE_RESET(mProfiler);
+    }
+
+    // Init for root with previous word
+    void initAsRootWithPreviousWord(DicNode *dicNode, const int rootGroupPos) {
+        mIsUsed = true;
+        mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
+        mDicNodeProperties.init(
+                NOT_A_DICT_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
+                true /* hasChildren */, false /* isBlacklistedOrNotAWord */,  0 /* depth */,
+                0 /* terminalDepth */);
+        // TODO: Move to dicNodeState?
+        mDicNodeState.mDicNodeStateOutput.init(); // reset for next word
+        mDicNodeState.mDicNodeStateInput.init(
+                &dicNode->mDicNodeState.mDicNodeStateInput, true /* resetTerminalDiffCost */);
+        mDicNodeState.mDicNodeStateScoring.init(
+                &dicNode->mDicNodeState.mDicNodeStateScoring);
+        mDicNodeState.mDicNodeStatePrevWord.init(
+                dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() + 1,
+                dicNode->mDicNodeProperties.getProbability(),
+                dicNode->mDicNodeProperties.getPos(),
+                dicNode->mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
+                dicNode->mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(),
+                dicNode->getOutputWordBuf(),
+                dicNode->mDicNodeProperties.getDepth(),
+                dicNode->mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex(),
+                mDicNodeState.mDicNodeStateInput.getInputIndex(0) /* lastInputIndex */);
+        PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
+    }
+
+    void initAsPassingChild(DicNode *parentNode) {
+        mIsUsed = true;
+        mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
+        const int c = parentNode->getNodeTypedCodePoint();
+        mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
+        mDicNodeState.init(&parentNode->mDicNodeState);
+        PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
+    }
+
+    void initAsChild(const DicNode *const dicNode, const int pos, const int childrenPos,
+            const int probability, const bool isTerminal, const bool hasChildren,
+            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
+            const int *const mergedNodeCodePoints) {
+        mIsUsed = true;
+        uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
+        mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
+        const uint16_t newLeavingDepth = static_cast<uint16_t>(
+                dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount);
+        mDicNodeProperties.init(pos, childrenPos, mergedNodeCodePoints[0], probability,
+                isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth, newLeavingDepth);
+        mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount,
+                mergedNodeCodePoints);
+        PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
+    }
+
+    AK_FORCE_INLINE void remove() {
+        mIsUsed = false;
+        if (mReleaseListener) {
+            mReleaseListener->onReleased(this);
+        }
+    }
+
+    bool isUsed() const {
+        return mIsUsed;
+    }
+
+    bool isRoot() const {
+        return getNodeCodePointCount() == 0;
+    }
+
+    bool hasChildren() const {
+        return mDicNodeProperties.hasChildren();
+    }
+
+    bool isLeavingNode() const {
+        ASSERT(getNodeCodePointCount() <= mDicNodeProperties.getLeavingDepth());
+        return getNodeCodePointCount() == mDicNodeProperties.getLeavingDepth();
+    }
+
+    AK_FORCE_INLINE bool isFirstLetter() const {
+        return getNodeCodePointCount() == 1;
+    }
+
+    bool isCached() const {
+        return mIsCachedForNextSuggestion;
+    }
+
+    void setCached() {
+        mIsCachedForNextSuggestion = true;
+    }
+
+    // Used to expand the node in DicNodeUtils
+    int getNodeTypedCodePoint() const {
+        return mDicNodeState.mDicNodeStateOutput.getCodePointAt(getNodeCodePointCount());
+    }
+
+    // Check if the current word and the previous word can be considered as a valid multiple word
+    // suggestion.
+    bool isValidMultipleWordSuggestion() const {
+        if (isBlacklistedOrNotAWord()) {
+            return false;
+        }
+        // Treat suggestion as invalid if the current and the previous word are single character
+        // words.
+        const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
+                - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
+        const int currentWordLen = getNodeCodePointCount();
+        return (prevWordLen != 1 || currentWordLen != 1);
+    }
+
+    bool isFirstCharUppercase() const {
+        const int c = getOutputWordBuf()[0];
+        return CharUtils::isAsciiUpper(c);
+    }
+
+    bool isFirstWord() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_DICT_POS;
+    }
+
+    bool isCompletion(const int inputSize) const {
+        return mDicNodeState.mDicNodeStateInput.getInputIndex(0) >= inputSize;
+    }
+
+    bool canDoLookAheadCorrection(const int inputSize) const {
+        return mDicNodeState.mDicNodeStateInput.getInputIndex(0) < inputSize - 1;
+    }
+
+    // Used to get bigram probability in DicNodeUtils
+    int getPos() const {
+        return mDicNodeProperties.getPos();
+    }
+
+    // Used to get bigram probability in DicNodeUtils
+    int getPrevWordPos() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
+    }
+
+    // Used in DicNodeUtils
+    int getChildrenPos() const {
+        return mDicNodeProperties.getChildrenPos();
+    }
+
+    int getProbability() const {
+        return mDicNodeProperties.getProbability();
+    }
+
+    AK_FORCE_INLINE bool isTerminalWordNode() const {
+        const bool isTerminalNodes = mDicNodeProperties.isTerminal();
+        const int currentNodeDepth = getNodeCodePointCount();
+        const int terminalNodeDepth = mDicNodeProperties.getLeavingDepth();
+        return isTerminalNodes && currentNodeDepth > 0 && currentNodeDepth == terminalNodeDepth;
+    }
+
+    bool shouldBeFilteredBySafetyNetForBigram() const {
+        const uint16_t currentDepth = getNodeCodePointCount();
+        const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
+                - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
+        return !(currentDepth > 0 && (currentDepth != 1 || prevWordLen != 1));
+    }
+
+    bool isTotalInputSizeExceedingLimit() const {
+        const int prevWordsLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
+        const int currentWordDepth = getNodeCodePointCount();
+        // TODO: 3 can be 2? Needs to be investigated.
+        // TODO: Have a const variable for 3 (or 2)
+        return prevWordsLen + currentWordDepth > MAX_WORD_LENGTH - 3;
+    }
+
+    // TODO: This may be defective. Needs to be revised.
+    bool truncateNode(const DicNode *const topNode, const int inputCommitPoint) {
+        const int prevWordLenOfTop = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
+        int newPrevWordStartIndex = inputCommitPoint;
+        int charCount = 0;
+        // Find new word start index
+        for (int i = 0; i < prevWordLenOfTop; ++i) {
+            const int c = mDicNodeState.mDicNodeStatePrevWord.getPrevWordCodePointAt(i);
+            // TODO: Check other separators.
+            if (c != KEYCODE_SPACE && c != KEYCODE_SINGLE_QUOTE) {
+                if (charCount == inputCommitPoint) {
+                    newPrevWordStartIndex = i;
+                    break;
+                }
+                ++charCount;
+            }
+        }
+        if (!mDicNodeState.mDicNodeStatePrevWord.startsWith(
+                &topNode->mDicNodeState.mDicNodeStatePrevWord, newPrevWordStartIndex - 1)) {
+            // Node mismatch.
+            return false;
+        }
+        mDicNodeState.mDicNodeStateInput.truncate(inputCommitPoint);
+        mDicNodeState.mDicNodeStatePrevWord.truncate(newPrevWordStartIndex);
+        return true;
+    }
+
+    void outputResult(int *dest) const {
+        const uint16_t prevWordLength = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
+        const uint16_t currentDepth = getNodeCodePointCount();
+        DicNodeUtils::appendTwoWords(mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
+                   prevWordLength, getOutputWordBuf(), currentDepth, dest);
+        DUMP_WORD_AND_SCORE("OUTPUT");
+    }
+
+    // "Total" in this context (and other methods in this class) means the whole suggestion. When
+    // this represents a multi-word suggestion, the referenced PtNode (in mDicNodeState) is only
+    // the one that corresponds to the last word of the suggestion, and all the previous words
+    // are concatenated together in mPrevWord - which contains a space at the end.
+    int getTotalNodeSpaceCount() const {
+        if (isFirstWord()) return 0;
+        return CharUtils::getSpaceCount(mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
+                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength());
+    }
+
+    int getSecondWordFirstInputIndex(const ProximityInfoState *const pInfoState) const {
+        const int inputIndex = mDicNodeState.mDicNodeStatePrevWord.getSecondWordFirstInputIndex();
+        if (inputIndex == NOT_AN_INDEX) {
+            return NOT_AN_INDEX;
+        } else {
+            return pInfoState->getInputIndexOfSampledPoint(inputIndex);
+        }
+    }
+
+    bool hasMultipleWords() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() > 0;
+    }
+
+    int getProximityCorrectionCount() const {
+        return mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount();
+    }
+
+    int getEditCorrectionCount() const {
+        return mDicNodeState.mDicNodeStateScoring.getEditCorrectionCount();
+    }
+
+    // Used to prune nodes
+    float getNormalizedCompoundDistance() const {
+        return mDicNodeState.mDicNodeStateScoring.getNormalizedCompoundDistance();
+    }
+
+    // Used to prune nodes
+    float getNormalizedSpatialDistance() const {
+        return mDicNodeState.mDicNodeStateScoring.getSpatialDistance()
+                / static_cast<float>(getInputIndex(0) + 1);
+    }
+
+    // Used to prune nodes
+    float getCompoundDistance() const {
+        return mDicNodeState.mDicNodeStateScoring.getCompoundDistance();
+    }
+
+    // Used to prune nodes
+    float getCompoundDistance(const float languageWeight) const {
+        return mDicNodeState.mDicNodeStateScoring.getCompoundDistance(languageWeight);
+    }
+
+    // Used to commit input partially
+    int getPrevWordNodePos() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
+    }
+
+    AK_FORCE_INLINE const int *getOutputWordBuf() const {
+        return mDicNodeState.mDicNodeStateOutput.mCodePointsBuf;
+    }
+
+    int getPrevCodePointG(int pointerId) const {
+        return mDicNodeState.mDicNodeStateInput.getPrevCodePoint(pointerId);
+    }
+
+    // Whether the current codepoint can be an intentional omission, in which case the traversal
+    // algorithm will always check for a possible omission here.
+    bool canBeIntentionalOmission() const {
+        return CharUtils::isIntentionalOmissionCodePoint(getNodeCodePoint());
+    }
+
+    // Whether the omission is so frequent that it should incur zero cost.
+    bool isZeroCostOmission() const {
+        // TODO: do not hardcode and read from header
+        return (getNodeCodePoint() == KEYCODE_SINGLE_QUOTE);
+    }
+
+    // TODO: remove
+    float getTerminalDiffCostG(int path) const {
+        return mDicNodeState.mDicNodeStateInput.getTerminalDiffCost(path);
+    }
+
+    //////////////////////
+    // Temporary getter //
+    // TODO: Remove     //
+    //////////////////////
+    // TODO: Remove once touch path is merged into ProximityInfoState
+    // Note: Returned codepoint may be a digraph codepoint if the node is in a composite glyph.
+    int getNodeCodePoint() const {
+        const int codePoint = mDicNodeProperties.getNodeCodePoint();
+        const DigraphUtils::DigraphCodePointIndex digraphIndex =
+                mDicNodeState.mDicNodeStateScoring.getDigraphIndex();
+        if (digraphIndex == DigraphUtils::NOT_A_DIGRAPH_INDEX) {
+            return codePoint;
+        }
+        return DigraphUtils::getDigraphCodePointForIndex(codePoint, digraphIndex);
+    }
+
+    ////////////////////////////////
+    // Utils for cost calculation //
+    ////////////////////////////////
+    AK_FORCE_INLINE bool isSameNodeCodePoint(const DicNode *const dicNode) const {
+        return mDicNodeProperties.getNodeCodePoint()
+                == dicNode->mDicNodeProperties.getNodeCodePoint();
+    }
+
+    // TODO: remove
+    // TODO: rename getNextInputIndex
+    int16_t getInputIndex(int pointerId) const {
+        return mDicNodeState.mDicNodeStateInput.getInputIndex(pointerId);
+    }
+
+    ////////////////////////////////////
+    // Getter of features for scoring //
+    ////////////////////////////////////
+    float getSpatialDistanceForScoring() const {
+        return mDicNodeState.mDicNodeStateScoring.getSpatialDistance();
+    }
+
+    float getLanguageDistanceForScoring() const {
+        return mDicNodeState.mDicNodeStateScoring.getLanguageDistance();
+    }
+
+    // For space-aware gestures, we store the normalized distance at the char index
+    // that ends the first word of the suggestion. We call this the distance after
+    // first word.
+    float getNormalizedCompoundDistanceAfterFirstWord() const {
+        return mDicNodeState.mDicNodeStateScoring.getNormalizedCompoundDistanceAfterFirstWord();
+    }
+
+    float getLanguageDistanceRatePerWordForScoring() const {
+        const float langDist = getLanguageDistanceForScoring();
+        const float totalWordCount =
+                static_cast<float>(mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() + 1);
+        return langDist / totalWordCount;
+    }
+
+    float getRawLength() const {
+        return mDicNodeState.mDicNodeStateScoring.getRawLength();
+    }
+
+    bool isLessThanOneErrorForScoring() const {
+        return mDicNodeState.mDicNodeStateScoring.getEditCorrectionCount()
+                + mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount() <= 1;
+    }
+
+    DoubleLetterLevel getDoubleLetterLevel() const {
+        return mDicNodeState.mDicNodeStateScoring.getDoubleLetterLevel();
+    }
+
+    void setDoubleLetterLevel(DoubleLetterLevel doubleLetterLevel) {
+        mDicNodeState.mDicNodeStateScoring.setDoubleLetterLevel(doubleLetterLevel);
+    }
+
+    bool isInDigraph() const {
+        return mDicNodeState.mDicNodeStateScoring.getDigraphIndex()
+                != DigraphUtils::NOT_A_DIGRAPH_INDEX;
+    }
+
+    void advanceDigraphIndex() {
+        mDicNodeState.mDicNodeStateScoring.advanceDigraphIndex();
+    }
+
+    bool isExactMatch() const {
+        return mDicNodeState.mDicNodeStateScoring.isExactMatch();
+    }
+
+    bool isBlacklistedOrNotAWord() const {
+        return mDicNodeProperties.isBlacklistedOrNotAWord();
+    }
+
+    inline uint16_t getNodeCodePointCount() const {
+        return mDicNodeProperties.getDepth();
+    }
+
+    // Returns code point count including spaces
+    inline uint16_t getTotalNodeCodePointCount() const {
+        return getNodeCodePointCount() + mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
+    }
+
+    AK_FORCE_INLINE void dump(const char *tag) const {
+#if DEBUG_DICT
+        DUMP_WORD_AND_SCORE(tag);
+#if DEBUG_DUMP_ERROR
+        mProfiler.dump();
+#endif
+#endif
+    }
+
+    void setReleaseListener(DicNodeReleaseListener *releaseListener) {
+        mReleaseListener = releaseListener;
+    }
+
+    AK_FORCE_INLINE bool compare(const DicNode *right) {
+        if (!isUsed() && !right->isUsed()) {
+            // Compare pointer values here for stable comparison
+            return this > right;
+        }
+        if (!isUsed()) {
+            return true;
+        }
+        if (!right->isUsed()) {
+            return false;
+        }
+        // Promote exact matches to prevent them from being pruned.
+        const bool leftExactMatch = isExactMatch();
+        const bool rightExactMatch = right->isExactMatch();
+        if (leftExactMatch != rightExactMatch) {
+            return leftExactMatch;
+        }
+        const float diff =
+                right->getNormalizedCompoundDistance() - getNormalizedCompoundDistance();
+        static const float MIN_DIFF = 0.000001f;
+        if (diff > MIN_DIFF) {
+            return true;
+        } else if (diff < -MIN_DIFF) {
+            return false;
+        }
+        const int depth = getNodeCodePointCount();
+        const int depthDiff = right->getNodeCodePointCount() - depth;
+        if (depthDiff != 0) {
+            return depthDiff > 0;
+        }
+        for (int i = 0; i < depth; ++i) {
+            const int codePoint = mDicNodeState.mDicNodeStateOutput.getCodePointAt(i);
+            const int rightCodePoint = right->mDicNodeState.mDicNodeStateOutput.getCodePointAt(i);
+            if (codePoint != rightCodePoint) {
+                return rightCodePoint > codePoint;
+            }
+        }
+        // Compare pointer values here for stable comparison
+        return this > right;
+    }
+
+ private:
+    DicNodeProperties mDicNodeProperties;
+    DicNodeState mDicNodeState;
+    // TODO: Remove
+    bool mIsCachedForNextSuggestion;
+    bool mIsUsed;
+    DicNodeReleaseListener *mReleaseListener;
+
+    AK_FORCE_INLINE int getTotalInputIndex() const {
+        int index = 0;
+        for (int i = 0; i < MAX_POINTER_COUNT_G; i++) {
+            index += mDicNodeState.mDicNodeStateInput.getInputIndex(i);
+        }
+        return index;
+    }
+
+    // Caveat: Must not be called outside Weighting
+    // This restriction is guaranteed by "friend"
+    AK_FORCE_INLINE void addCost(const float spatialCost, const float languageCost,
+            const bool doNormalization, const int inputSize, const ErrorType errorType) {
+        if (DEBUG_GEO_FULL) {
+            LOGI_SHOW_ADD_COST_PROP;
+        }
+        mDicNodeState.mDicNodeStateScoring.addCost(spatialCost, languageCost, doNormalization,
+                inputSize, getTotalInputIndex(), errorType);
+    }
+
+    // Saves the current normalized compound distance for space-aware gestures.
+    // See getNormalizedCompoundDistanceAfterFirstWord for details.
+    AK_FORCE_INLINE void saveNormalizedCompoundDistanceAfterFirstWordIfNoneYet() {
+        mDicNodeState.mDicNodeStateScoring.saveNormalizedCompoundDistanceAfterFirstWordIfNoneYet();
+    }
+
+    // Caveat: Must not be called outside Weighting
+    // This restriction is guaranteed by "friend"
+    AK_FORCE_INLINE void forwardInputIndex(const int pointerId, const int count,
+            const bool overwritesPrevCodePointByNodeCodePoint) {
+        if (count == 0) {
+            return;
+        }
+        mDicNodeState.mDicNodeStateInput.forwardInputIndex(pointerId, count);
+        if (overwritesPrevCodePointByNodeCodePoint) {
+            mDicNodeState.mDicNodeStateInput.setPrevCodePoint(0, getNodeCodePoint());
+        }
+    }
+
+    AK_FORCE_INLINE void updateInputIndexG(const DicNode_InputStateG *const inputStateG) {
+        if (mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() == 1 && isFirstLetter()) {
+            mDicNodeState.mDicNodeStatePrevWord.setSecondWordFirstInputIndex(
+                    inputStateG->mInputIndex);
+        }
+        mDicNodeState.mDicNodeStateInput.updateInputIndexG(inputStateG->mPointerId,
+                inputStateG->mInputIndex, inputStateG->mPrevCodePoint,
+                inputStateG->mTerminalDiffCost, inputStateG->mRawLength);
+        mDicNodeState.mDicNodeStateScoring.addRawLength(inputStateG->mRawLength);
+        mDicNodeState.mDicNodeStateScoring.setDoubleLetterLevel(inputStateG->mDoubleLetterLevel);
+    }
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_H
diff --git a/src/aosp/suggest/core/dicnode/dic_node_priority_queue.h b/src/aosp/suggest/core/dicnode/dic_node_priority_queue.h
new file mode 100644
index 0000000..7461f0c
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_node_priority_queue.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_PRIORITY_QUEUE_H
+#define LATINIME_DIC_NODE_PRIORITY_QUEUE_H
+
+#include <queue>
+#include <vector>
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_release_listener.h"
+
+namespace latinime {
+
+class DicNodePriorityQueue : public DicNodeReleaseListener {
+ public:
+    AK_FORCE_INLINE explicit DicNodePriorityQueue(const int capacity)
+            : mCapacity(capacity), mMaxSize(capacity), mDicNodesBuf(),
+              mUnusedNodeIndices(), mNextUnusedNodeId(0), mDicNodesQueue() {
+        mDicNodesBuf.resize(mCapacity + 1);
+        mUnusedNodeIndices.resize(mCapacity + 1);
+        clearAndResizeToCapacity();
+    }
+
+    // Non virtual inline destructor -- never inherit this class
+    AK_FORCE_INLINE ~DicNodePriorityQueue() {}
+
+    int getSize() const {
+        return static_cast<int>(mDicNodesQueue.size());
+    }
+
+    int getMaxSize() const {
+        return mMaxSize;
+    }
+
+    AK_FORCE_INLINE void setMaxSize(const int maxSize) {
+        ASSERT(maxSize <= mCapacity);
+        mMaxSize = min(maxSize, mCapacity);
+    }
+
+    AK_FORCE_INLINE void clearAndResizeToCapacity() {
+        clearAndResize(mCapacity);
+    }
+
+    AK_FORCE_INLINE void clear() {
+        clearAndResize(mMaxSize);
+    }
+
+    AK_FORCE_INLINE void clearAndResize(const int maxSize) {
+        ASSERT(maxSize <= mCapacity);
+        while (!mDicNodesQueue.empty()) {
+            mDicNodesQueue.pop();
+        }
+        setMaxSize(maxSize);
+        for (int i = 0; i < mCapacity + 1; ++i) {
+            mDicNodesBuf[i].remove();
+            mDicNodesBuf[i].setReleaseListener(this);
+            mUnusedNodeIndices[i] = i == mCapacity ? NOT_A_NODE_ID : static_cast<int>(i) + 1;
+        }
+        mNextUnusedNodeId = 0;
+    }
+
+    // Copy
+    AK_FORCE_INLINE DicNode *copyPush(DicNode *dicNode) {
+        return copyPush(dicNode, mMaxSize);
+    }
+
+    AK_FORCE_INLINE void copyPop(DicNode *dest) {
+        if (mDicNodesQueue.empty()) {
+            ASSERT(false);
+            return;
+        }
+        DicNode *node = mDicNodesQueue.top();
+        if (dest) {
+            DicNodeUtils::initByCopy(node, dest);
+        }
+        node->remove();
+        mDicNodesQueue.pop();
+    }
+
+    void onReleased(DicNode *dicNode) {
+        const int index = static_cast<int>(dicNode - &mDicNodesBuf[0]);
+        if (mUnusedNodeIndices[index] != NOT_A_NODE_ID) {
+            // it's already released
+            return;
+        }
+        mUnusedNodeIndices[index] = mNextUnusedNodeId;
+        mNextUnusedNodeId = index;
+        ASSERT(index >= 0 && index < (mCapacity + 1));
+    }
+
+    AK_FORCE_INLINE void dump() const {
+        AKLOGI("\n\n\n\n\n===========================");
+        for (int i = 0; i < mCapacity + 1; ++i) {
+            if (mDicNodesBuf[i].isUsed()) {
+                mDicNodesBuf[i].dump("QUEUE: ");
+            }
+        }
+        AKLOGI("===========================\n\n\n\n\n");
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodePriorityQueue);
+    static const int NOT_A_NODE_ID = -1;
+
+    AK_FORCE_INLINE static bool compareDicNode(DicNode *left, DicNode *right) {
+        return left->compare(right);
+    }
+
+    struct DicNodeComparator {
+        bool operator ()(DicNode *left, DicNode *right) {
+            return compareDicNode(left, right);
+        }
+    };
+
+    typedef std::priority_queue<DicNode *, std::vector<DicNode *>, DicNodeComparator> DicNodesQueue;
+    const int mCapacity;
+    int mMaxSize;
+    std::vector<DicNode> mDicNodesBuf; // of each element of mDicNodesBuf respectively
+    std::vector<int> mUnusedNodeIndices;
+    int mNextUnusedNodeId;
+    DicNodesQueue mDicNodesQueue;
+
+    inline bool isFull(const int maxSize) const {
+        return getSize() >= maxSize;
+    }
+
+    AK_FORCE_INLINE void pop() {
+        copyPop(0);
+    }
+
+    AK_FORCE_INLINE bool betterThanWorstDicNode(DicNode *dicNode) const {
+        DicNode *worstNode = mDicNodesQueue.top();
+        if (!worstNode) {
+            return true;
+        }
+        return compareDicNode(dicNode, worstNode);
+    }
+
+    AK_FORCE_INLINE DicNode *searchEmptyDicNode() {
+        if (mCapacity == 0) {
+            return 0;
+        }
+        if (mNextUnusedNodeId == NOT_A_NODE_ID) {
+            AKLOGI("No unused node found.");
+            for (int i = 0; i < mCapacity + 1; ++i) {
+                AKLOGI("Dump node availability, %d, %d, %d",
+                        i, mDicNodesBuf[i].isUsed(), mUnusedNodeIndices[i]);
+            }
+            ASSERT(false);
+            return 0;
+        }
+        DicNode *dicNode = &mDicNodesBuf[mNextUnusedNodeId];
+        markNodeAsUsed(dicNode);
+        return dicNode;
+    }
+
+    AK_FORCE_INLINE void markNodeAsUsed(DicNode *dicNode) {
+        const int index = static_cast<int>(dicNode - &mDicNodesBuf[0]);
+        mNextUnusedNodeId = mUnusedNodeIndices[index];
+        mUnusedNodeIndices[index] = NOT_A_NODE_ID;
+        ASSERT(index >= 0 && index < (mCapacity + 1));
+    }
+
+    AK_FORCE_INLINE DicNode *pushPoolNodeWithMaxSize(DicNode *dicNode, const int maxSize) {
+        if (!dicNode) {
+            return 0;
+        }
+        if (!isFull(maxSize)) {
+            mDicNodesQueue.push(dicNode);
+            return dicNode;
+        }
+        if (betterThanWorstDicNode(dicNode)) {
+            pop();
+            mDicNodesQueue.push(dicNode);
+            return dicNode;
+        }
+        dicNode->remove();
+        return 0;
+    }
+
+    // Copy
+    AK_FORCE_INLINE DicNode *copyPush(DicNode *dicNode, const int maxSize) {
+        return pushPoolNodeWithMaxSize(newDicNode(dicNode), maxSize);
+    }
+
+    AK_FORCE_INLINE DicNode *newDicNode(DicNode *dicNode) {
+        DicNode *newNode = searchEmptyDicNode();
+        if (newNode) {
+            DicNodeUtils::initByCopy(dicNode, newNode);
+        }
+        return newNode;
+    }
+
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_PRIORITY_QUEUE_H
diff --git a/src/aosp/suggest/core/dicnode/dic_node_profiler.h b/src/aosp/suggest/core/dicnode/dic_node_profiler.h
new file mode 100644
index 0000000..1f4d257
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_node_profiler.h
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_PROFILER_H
+#define LATINIME_DIC_NODE_PROFILER_H
+
+#include "defines.h"
+
+#if DEBUG_DICT
+#define PROF_SPACE_SUBSTITUTION(profiler) profiler.profSpaceSubstitution()
+#define PROF_SPACE_OMISSION(profiler) profiler.profSpaceOmission()
+#define PROF_ADDITIONAL_PROXIMITY(profiler) profiler.profAdditionalProximity()
+#define PROF_SUBSTITUTION(profiler) profiler.profSubstitution()
+#define PROF_OMISSION(profiler) profiler.profOmission()
+#define PROF_INSERTION(profiler) profiler.profInsertion()
+#define PROF_MATCH(profiler) profiler.profMatch()
+#define PROF_COMPLETION(profiler) profiler.profCompletion()
+#define PROF_TRANSPOSITION(profiler) profiler.profTransposition()
+#define PROF_NEARESTKEY(profiler) profiler.profNearestKey()
+#define PROF_TERMINAL(profiler) profiler.profTerminal()
+#define PROF_TERMINAL_INSERTION(profiler) profiler.profTerminalInsertion()
+#define PROF_NEW_WORD(profiler) profiler.profNewWord()
+#define PROF_NEW_WORD_BIGRAM(profiler) profiler.profNewWordBigram()
+#define PROF_NODE_RESET(profiler) profiler.reset()
+#define PROF_NODE_COPY(src, dest) dest.copy(src)
+#else
+#define PROF_SPACE_SUBSTITUTION(profiler)
+#define PROF_SPACE_OMISSION(profiler)
+#define PROF_ADDITONAL_PROXIMITY(profiler)
+#define PROF_SUBSTITUTION(profiler)
+#define PROF_OMISSION(profiler)
+#define PROF_INSERTION(profiler)
+#define PROF_MATCH(profiler)
+#define PROF_COMPLETION(profiler)
+#define PROF_TRANSPOSITION(profiler)
+#define PROF_NEARESTKEY(profiler)
+#define PROF_TERMINAL(profiler)
+#define PROF_TERMINAL_INSERTION(profiler)
+#define PROF_NEW_WORD(profiler)
+#define PROF_NEW_WORD_BIGRAM(profiler)
+#define PROF_NODE_RESET(profiler)
+#define PROF_NODE_COPY(src, dest)
+#endif
+
+namespace latinime {
+
+class DicNodeProfiler {
+ public:
+#if DEBUG_DICT
+    AK_FORCE_INLINE DicNodeProfiler()
+            : mProfOmission(0), mProfInsertion(0), mProfTransposition(0),
+              mProfAdditionalProximity(0), mProfSubstitution(0),
+              mProfSpaceSubstitution(0), mProfSpaceOmission(0),
+              mProfMatch(0), mProfCompletion(0), mProfTerminal(0), mProfTerminalInsertion(0),
+              mProfNearestKey(0), mProfNewWord(0), mProfNewWordBigram(0) {}
+
+    int mProfOmission;
+    int mProfInsertion;
+    int mProfTransposition;
+    int mProfAdditionalProximity;
+    int mProfSubstitution;
+    int mProfSpaceSubstitution;
+    int mProfSpaceOmission;
+    int mProfMatch;
+    int mProfCompletion;
+    int mProfTerminal;
+    int mProfTerminalInsertion;
+    int mProfNearestKey;
+    int mProfNewWord;
+    int mProfNewWordBigram;
+
+    void profSpaceSubstitution() {
+        ++mProfSpaceSubstitution;
+    }
+
+    void profSpaceOmission() {
+        ++mProfSpaceOmission;
+    }
+
+    void profAdditionalProximity() {
+        ++mProfAdditionalProximity;
+    }
+
+    void profSubstitution() {
+        ++mProfSubstitution;
+    }
+
+    void profOmission() {
+        ++mProfOmission;
+    }
+
+    void profInsertion() {
+        ++mProfInsertion;
+    }
+
+    void profMatch() {
+        ++mProfMatch;
+    }
+
+    void profCompletion() {
+        ++mProfCompletion;
+    }
+
+    void profTransposition() {
+        ++mProfTransposition;
+    }
+
+    void profNearestKey() {
+        ++mProfNearestKey;
+    }
+
+    void profTerminal() {
+        ++mProfTerminal;
+    }
+
+    void profTerminalInsertion() {
+        ++mProfTerminalInsertion;
+    }
+
+    void profNewWord() {
+        ++mProfNewWord;
+    }
+
+    void profNewWordBigram() {
+        ++mProfNewWordBigram;
+    }
+
+    void reset() {
+        mProfSpaceSubstitution = 0;
+        mProfSpaceOmission = 0;
+        mProfAdditionalProximity = 0;
+        mProfSubstitution = 0;
+        mProfOmission = 0;
+        mProfInsertion = 0;
+        mProfMatch = 0;
+        mProfCompletion = 0;
+        mProfTransposition = 0;
+        mProfNearestKey = 0;
+        mProfTerminal = 0;
+        mProfNewWord = 0;
+        mProfNewWordBigram = 0;
+    }
+
+    void copy(const DicNodeProfiler *const profiler) {
+        mProfSpaceSubstitution = profiler->mProfSpaceSubstitution;
+        mProfSpaceOmission = profiler->mProfSpaceOmission;
+        mProfAdditionalProximity = profiler->mProfAdditionalProximity;
+        mProfSubstitution = profiler->mProfSubstitution;
+        mProfOmission = profiler->mProfOmission;
+        mProfInsertion = profiler->mProfInsertion;
+        mProfMatch = profiler->mProfMatch;
+        mProfCompletion = profiler->mProfCompletion;
+        mProfTransposition = profiler->mProfTransposition;
+        mProfNearestKey = profiler->mProfNearestKey;
+        mProfTerminal = profiler->mProfTerminal;
+        mProfNewWord = profiler->mProfNewWord;
+        mProfNewWordBigram = profiler->mProfNewWordBigram;
+    }
+
+    void dump() const {
+        AKLOGI("O %d, I %d, T %d, AP %d, S %d, SS %d, SO %d, M %d, C %d, TE %d, NW = %d, NWB = %d",
+                mProfOmission, mProfInsertion, mProfTransposition, mProfAdditionalProximity,
+                mProfSubstitution, mProfSpaceSubstitution, mProfSpaceOmission, mProfMatch,
+                mProfCompletion, mProfTerminal, mProfNewWord, mProfNewWordBigram);
+    }
+#else
+    DicNodeProfiler() {}
+#endif
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+};
+}
+#endif // LATINIME_DIC_NODE_PROFILER_H
diff --git a/src/aosp/suggest/core/dicnode/dic_node_release_listener.h b/src/aosp/suggest/core/dicnode/dic_node_release_listener.h
new file mode 100644
index 0000000..2ca4f21
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_node_release_listener.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_RELEASE_LISTENER_H
+#define LATINIME_DIC_NODE_RELEASE_LISTENER_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicNode;
+
+class DicNodeReleaseListener {
+ public:
+    DicNodeReleaseListener() {}
+    virtual ~DicNodeReleaseListener() {}
+    virtual void onReleased(DicNode *dicNode) = 0;
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DicNodeReleaseListener);
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_RELEASE_LISTENER_H
diff --git a/src/aosp/suggest/core/dicnode/dic_node_utils.cpp b/src/aosp/suggest/core/dicnode/dic_node_utils.cpp
new file mode 100644
index 0000000..ec65114
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_node_utils.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dicnode/dic_node_utils.h"
+
+#include <cstring>
+
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/multi_bigram_map.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "utils/char_utils.h"
+
+namespace latinime {
+
+///////////////////////////////
+// Node initialization utils //
+///////////////////////////////
+
+/* static */ void DicNodeUtils::initAsRoot(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+        const int prevWordNodePos, DicNode *const newRootNode) {
+    newRootNode->initAsRoot(dictionaryStructurePolicy->getRootPosition(), prevWordNodePos);
+}
+
+/*static */ void DicNodeUtils::initAsRootWithPreviousWord(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+        DicNode *const prevWordLastNode, DicNode *const newRootNode) {
+    newRootNode->initAsRootWithPreviousWord(
+            prevWordLastNode, dictionaryStructurePolicy->getRootPosition());
+}
+
+/* static */ void DicNodeUtils::initByCopy(DicNode *srcNode, DicNode *destNode) {
+    destNode->initByCopy(srcNode);
+}
+
+///////////////////////////////////
+// Traverse node expansion utils //
+///////////////////////////////////
+/* static */ void DicNodeUtils::getAllChildDicNodes(DicNode *dicNode,
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+        DicNodeVector *childDicNodes) {
+    if (dicNode->isTotalInputSizeExceedingLimit()) {
+        return;
+    }
+    if (!dicNode->isLeavingNode()) {
+        childDicNodes->pushPassingChild(dicNode);
+    } else {
+        dictionaryStructurePolicy->createAndGetAllChildNodes(dicNode, childDicNodes);
+    }
+}
+
+///////////////////
+// Scoring utils //
+///////////////////
+/**
+ * Computes the combined bigram / unigram cost for the given dicNode.
+ */
+/* static */ float DicNodeUtils::getBigramNodeImprobability(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+        const DicNode *const node, MultiBigramMap *multiBigramMap) {
+    if (node->hasMultipleWords() && !node->isValidMultipleWordSuggestion()) {
+        return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+    }
+    const int probability = getBigramNodeProbability(dictionaryStructurePolicy, node,
+            multiBigramMap);
+    // TODO: This equation to calculate the improbability looks unreasonable.  Investigate this.
+    const float cost = static_cast<float>(MAX_PROBABILITY - probability)
+            / static_cast<float>(MAX_PROBABILITY);
+    return cost;
+}
+
+/* static */ int DicNodeUtils::getBigramNodeProbability(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+        const DicNode *const node, MultiBigramMap *multiBigramMap) {
+    const int unigramProbability = node->getProbability();
+    const int wordPos = node->getPos();
+    const int prevWordPos = node->getPrevWordPos();
+    if (NOT_A_DICT_POS == wordPos || NOT_A_DICT_POS == prevWordPos) {
+        // Note: Normally wordPos comes from the dictionary and should never equal
+        // NOT_A_VALID_WORD_POS.
+        return dictionaryStructurePolicy->getProbability(unigramProbability,
+                NOT_A_PROBABILITY);
+    }
+    if (multiBigramMap) {
+        return multiBigramMap->getBigramProbability(dictionaryStructurePolicy, prevWordPos,
+                wordPos, unigramProbability);
+    }
+    return dictionaryStructurePolicy->getProbability(unigramProbability,
+            NOT_A_PROBABILITY);
+}
+
+////////////////
+// Char utils //
+////////////////
+
+// TODO: Move to char_utils?
+/* static */ int DicNodeUtils::appendTwoWords(const int *const src0, const int16_t length0,
+        const int *const src1, const int16_t length1, int *dest) {
+    int actualLength0 = 0;
+    for (int i = 0; i < length0; ++i) {
+        if (src0[i] == 0) {
+            break;
+        }
+        actualLength0 = i + 1;
+    }
+    actualLength0 = min(actualLength0, MAX_WORD_LENGTH);
+    memcpy(dest, src0, actualLength0 * sizeof(dest[0]));
+    if (!src1 || length1 == 0) {
+        return actualLength0;
+    }
+    int actualLength1 = 0;
+    for (int i = 0; i < length1; ++i) {
+        if (src1[i] == 0) {
+            break;
+        }
+        actualLength1 = i + 1;
+    }
+    actualLength1 = min(actualLength1, MAX_WORD_LENGTH - actualLength0);
+    memcpy(&dest[actualLength0], src1, actualLength1 * sizeof(dest[0]));
+    return actualLength0 + actualLength1;
+}
+} // namespace latinime
diff --git a/src/aosp/suggest/core/dicnode/dic_node_utils.h b/src/aosp/suggest/core/dicnode/dic_node_utils.h
new file mode 100644
index 0000000..3fb351a
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_node_utils.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_UTILS_H
+#define LATINIME_DIC_NODE_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicNode;
+class DicNodeVector;
+class DictionaryStructureWithBufferPolicy;
+class MultiBigramMap;
+
+class DicNodeUtils {
+ public:
+    static int appendTwoWords(const int *src0, const int16_t length0, const int *src1,
+            const int16_t length1, int *dest);
+    static void initAsRoot(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+            const int prevWordNodePos, DicNode *newRootNode);
+    static void initAsRootWithPreviousWord(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+            DicNode *prevWordLastNode, DicNode *newRootNode);
+    static void initByCopy(DicNode *srcNode, DicNode *destNode);
+    static void getAllChildDicNodes(DicNode *dicNode,
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+            DicNodeVector *childDicNodes);
+    static float getBigramNodeImprobability(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+            const DicNode *const node, MultiBigramMap *const multiBigramMap);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodeUtils);
+    // Max number of bigrams to look up
+    static const int MAX_BIGRAMS_CONSIDERED_PER_CONTEXT = 500;
+
+    static int getBigramNodeProbability(
+            const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy,
+            const DicNode *const node, MultiBigramMap *multiBigramMap);
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_UTILS_H
diff --git a/src/aosp/suggest/core/dicnode/dic_node_vector.h b/src/aosp/suggest/core/dicnode/dic_node_vector.h
new file mode 100644
index 0000000..42addae
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_node_vector.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_VECTOR_H
+#define LATINIME_DIC_NODE_VECTOR_H
+
+#include <vector>
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node.h"
+
+namespace latinime {
+
+class DicNodeVector {
+ public:
+#ifdef FLAG_DBG
+    // 0 will introduce resizing the vector.
+    static const int DEFAULT_NODES_SIZE_FOR_OPTIMIZATION = 0;
+#else
+    static const int DEFAULT_NODES_SIZE_FOR_OPTIMIZATION = 60;
+#endif
+    AK_FORCE_INLINE DicNodeVector() : mDicNodes(0), mLock(false), mEmptyNode() {}
+
+    // Specify the capacity of the vector
+    AK_FORCE_INLINE DicNodeVector(const int size) : mDicNodes(0), mLock(false), mEmptyNode() {
+        mDicNodes.reserve(size);
+    }
+
+    // Non virtual inline destructor -- never inherit this class
+    AK_FORCE_INLINE ~DicNodeVector() {}
+
+    AK_FORCE_INLINE void clear() {
+        mDicNodes.clear();
+        mLock = false;
+    }
+
+    int getSizeAndLock() {
+        mLock = true;
+        return static_cast<int>(mDicNodes.size());
+    }
+
+    bool exceeds(const size_t limit) const {
+        return mDicNodes.size() >= limit;
+    }
+
+    void pushPassingChild(DicNode *dicNode) {
+        ASSERT(!mLock);
+        mDicNodes.push_back(mEmptyNode);
+        mDicNodes.back().initAsPassingChild(dicNode);
+    }
+
+    void pushLeavingChild(const DicNode *const dicNode, const int pos, const int childrenPos,
+            const int probability, const bool isTerminal, const bool hasChildren,
+            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
+            const int *const mergedNodeCodePoints) {
+        ASSERT(!mLock);
+        mDicNodes.push_back(mEmptyNode);
+        mDicNodes.back().initAsChild(dicNode, pos, childrenPos, probability, isTerminal,
+                hasChildren, isBlacklistedOrNotAWord, mergedNodeCodePointCount,
+                mergedNodeCodePoints);
+    }
+
+    DicNode *operator[](const int id) {
+        ASSERT(id < static_cast<int>(mDicNodes.size()));
+        return &mDicNodes[id];
+    }
+
+    DicNode *front() {
+        ASSERT(1 <= static_cast<int>(mDicNodes.size()));
+        return &mDicNodes[0];
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DicNodeVector);
+    std::vector<DicNode> mDicNodes;
+    bool mLock;
+    DicNode mEmptyNode;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_VECTOR_H
diff --git a/src/aosp/suggest/core/dicnode/dic_nodes_cache.cpp b/src/aosp/suggest/core/dicnode/dic_nodes_cache.cpp
new file mode 100644
index 0000000..b6be47e
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_nodes_cache.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <list>
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node_priority_queue.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dicnode/dic_nodes_cache.h"
+
+namespace latinime {
+
+// The biggest value among MAX_CACHE_DIC_NODE_SIZE, MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT, ...
+const int DicNodesCache::LARGE_PRIORITY_QUEUE_CAPACITY = 310;
+// Capacity for reducing memory footprint.
+const int DicNodesCache::SMALL_PRIORITY_QUEUE_CAPACITY = 100;
+
+/**
+ * Truncates all of the dicNodes so that they start at the given commit point.
+ * Only called for multi-word typing input.
+ */
+DicNode *DicNodesCache::setCommitPoint(int commitPoint) {
+    std::list<DicNode> dicNodesList;
+    while (mCachedDicNodesForContinuousSuggestion->getSize() > 0) {
+        DicNode dicNode;
+        mCachedDicNodesForContinuousSuggestion->copyPop(&dicNode);
+        dicNodesList.push_front(dicNode);
+    }
+
+    // Get the starting words of the top scoring dicNode (last dicNode popped from priority queue)
+    // up to the commit point. These words have already been committed to the text view.
+    DicNode *topDicNode = &dicNodesList.front();
+    DicNode topDicNodeCopy;
+    DicNodeUtils::initByCopy(topDicNode, &topDicNodeCopy);
+
+    // Keep only those dicNodes that match the same starting words.
+    std::list<DicNode>::iterator iter;
+    for (iter = dicNodesList.begin(); iter != dicNodesList.end(); iter++) {
+        DicNode *dicNode = &*iter;
+        if (dicNode->truncateNode(&topDicNodeCopy, commitPoint)) {
+            mCachedDicNodesForContinuousSuggestion->copyPush(dicNode);
+        } else {
+            // Top dicNode should be reprocessed.
+            ASSERT(dicNode != topDicNode);
+            DicNode::managedDelete(dicNode);
+        }
+    }
+    mInputIndex -= commitPoint;
+    return topDicNode;
+}
+}  // namespace latinime
diff --git a/src/aosp/suggest/core/dicnode/dic_nodes_cache.h b/src/aosp/suggest/core/dicnode/dic_nodes_cache.h
new file mode 100644
index 0000000..8493b6a
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/dic_nodes_cache.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODES_CACHE_H
+#define LATINIME_DIC_NODES_CACHE_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node_priority_queue.h"
+
+namespace latinime {
+
+class DicNode;
+
+/**
+ * Class for controlling dicNode search priority queue and lexicon trie traversal.
+ */
+class DicNodesCache {
+ public:
+    AK_FORCE_INLINE explicit DicNodesCache(const bool usesLargeCapacityCache)
+            : mUsesLargeCapacityCache(usesLargeCapacityCache),
+              mDicNodePriorityQueue0(getCacheCapacity()),
+              mDicNodePriorityQueue1(getCacheCapacity()),
+              mDicNodePriorityQueue2(getCacheCapacity()),
+              mDicNodePriorityQueueForTerminal(MAX_RESULTS),
+              mActiveDicNodes(&mDicNodePriorityQueue0),
+              mNextActiveDicNodes(&mDicNodePriorityQueue1),
+              mCachedDicNodesForContinuousSuggestion(&mDicNodePriorityQueue2),
+              mTerminalDicNodes(&mDicNodePriorityQueueForTerminal),
+              mInputIndex(0), mLastCachedInputIndex(0) {}
+
+    AK_FORCE_INLINE virtual ~DicNodesCache() {}
+
+    AK_FORCE_INLINE void reset(const int nextActiveSize, const int terminalSize) {
+        mInputIndex = 0;
+        mLastCachedInputIndex = 0;
+        // We want to use the max capacity for the current active dic node queue.
+        mActiveDicNodes->clearAndResizeToCapacity();
+        // nextActiveSize is used to limit the next iteration's active dic node size.
+        const int nextActiveSizeFittingToTheCapacity = min(nextActiveSize, getCacheCapacity());
+        mNextActiveDicNodes->clearAndResize(nextActiveSizeFittingToTheCapacity);
+        mTerminalDicNodes->clearAndResize(terminalSize);
+        // We want to use the max capacity for the cached dic nodes that will be used for the
+        // continuous suggestion.
+        mCachedDicNodesForContinuousSuggestion->clearAndResizeToCapacity();
+    }
+
+    AK_FORCE_INLINE void continueSearch() {
+        resetTemporaryCaches();
+        restoreActiveDicNodesFromCache();
+    }
+
+    AK_FORCE_INLINE void advanceActiveDicNodes() {
+        if (DEBUG_DICT) {
+            AKLOGI("Advance active %d nodes.", mNextActiveDicNodes->getSize());
+        }
+        if (DEBUG_DICT_FULL) {
+            mNextActiveDicNodes->dump();
+        }
+        mNextActiveDicNodes =
+                moveNodesAndReturnReusableEmptyQueue(mNextActiveDicNodes, &mActiveDicNodes);
+    }
+
+    DicNode *setCommitPoint(int commitPoint);
+
+    int activeSize() const { return mActiveDicNodes->getSize(); }
+    int terminalSize() const { return mTerminalDicNodes->getSize(); }
+    bool isLookAheadCorrectionInputIndex(const int inputIndex) const {
+        return inputIndex == mInputIndex - 1;
+    }
+    void advanceInputIndex(const int inputSize) {
+        if (mInputIndex < inputSize) {
+            mInputIndex++;
+        }
+    }
+
+    AK_FORCE_INLINE void copyPushTerminal(DicNode *dicNode) {
+        mTerminalDicNodes->copyPush(dicNode);
+    }
+
+    AK_FORCE_INLINE void copyPushActive(DicNode *dicNode) {
+        mActiveDicNodes->copyPush(dicNode);
+    }
+
+    AK_FORCE_INLINE bool copyPushContinue(DicNode *dicNode) {
+        return mCachedDicNodesForContinuousSuggestion->copyPush(dicNode);
+    }
+
+    AK_FORCE_INLINE void copyPushNextActive(DicNode *dicNode) {
+        DicNode *pushedDicNode = mNextActiveDicNodes->copyPush(dicNode);
+        if (!pushedDicNode) {
+            if (dicNode->isCached()) {
+                dicNode->remove();
+            }
+            // We simply drop any dic node that was not cached, ignoring the slim chance
+            // that one of its children represents what the user really wanted.
+        }
+    }
+
+    void popTerminal(DicNode *dest) {
+        mTerminalDicNodes->copyPop(dest);
+    }
+
+    void popActive(DicNode *dest) {
+        mActiveDicNodes->copyPop(dest);
+    }
+
+    bool hasCachedDicNodesForContinuousSuggestion() const {
+        return mCachedDicNodesForContinuousSuggestion
+                && mCachedDicNodesForContinuousSuggestion->getSize() > 0;
+    }
+
+    AK_FORCE_INLINE bool isCacheBorderForTyping(const int inputSize) const {
+        // TODO: Move this variable to header
+        static const int CACHE_BACK_LENGTH = 3;
+        const int cacheInputIndex = inputSize - CACHE_BACK_LENGTH;
+        const bool shouldCache = (cacheInputIndex == mInputIndex)
+                && (cacheInputIndex != mLastCachedInputIndex);
+        return shouldCache;
+    }
+
+    AK_FORCE_INLINE void updateLastCachedInputIndex() {
+        mLastCachedInputIndex = mInputIndex;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DicNodesCache);
+
+    AK_FORCE_INLINE void restoreActiveDicNodesFromCache() {
+        if (DEBUG_DICT) {
+            AKLOGI("Restore %d nodes. inputIndex = %d.",
+                    mCachedDicNodesForContinuousSuggestion->getSize(), mLastCachedInputIndex);
+        }
+        if (DEBUG_DICT_FULL || DEBUG_CACHE) {
+            mCachedDicNodesForContinuousSuggestion->dump();
+        }
+        mInputIndex = mLastCachedInputIndex;
+        mCachedDicNodesForContinuousSuggestion = moveNodesAndReturnReusableEmptyQueue(
+                mCachedDicNodesForContinuousSuggestion, &mActiveDicNodes);
+    }
+
+    AK_FORCE_INLINE static DicNodePriorityQueue *moveNodesAndReturnReusableEmptyQueue(
+            DicNodePriorityQueue *src, DicNodePriorityQueue **dest) {
+        const int srcMaxSize = src->getMaxSize();
+        const int destMaxSize = (*dest)->getMaxSize();
+        DicNodePriorityQueue *tmp = *dest;
+        *dest = src;
+        (*dest)->setMaxSize(destMaxSize);
+        tmp->clearAndResize(srcMaxSize);
+        return tmp;
+    }
+
+    AK_FORCE_INLINE int getCacheCapacity() const {
+        return mUsesLargeCapacityCache ?
+                LARGE_PRIORITY_QUEUE_CAPACITY : SMALL_PRIORITY_QUEUE_CAPACITY;
+    }
+
+    AK_FORCE_INLINE void resetTemporaryCaches() {
+        mActiveDicNodes->clear();
+        mNextActiveDicNodes->clear();
+        mTerminalDicNodes->clear();
+    }
+
+    static const int LARGE_PRIORITY_QUEUE_CAPACITY;
+    static const int SMALL_PRIORITY_QUEUE_CAPACITY;
+
+    const bool mUsesLargeCapacityCache;
+    // Instances
+    DicNodePriorityQueue mDicNodePriorityQueue0;
+    DicNodePriorityQueue mDicNodePriorityQueue1;
+    DicNodePriorityQueue mDicNodePriorityQueue2;
+    DicNodePriorityQueue mDicNodePriorityQueueForTerminal;
+
+    // Active dicNodes currently being expanded.
+    DicNodePriorityQueue *mActiveDicNodes;
+    // Next dicNodes to be expanded.
+    DicNodePriorityQueue *mNextActiveDicNodes;
+    // Cached dicNodes used for continuous suggestion.
+    DicNodePriorityQueue *mCachedDicNodesForContinuousSuggestion;
+    // Current top terminal dicNodes.
+    DicNodePriorityQueue *mTerminalDicNodes;
+    int mInputIndex;
+    int mLastCachedInputIndex;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODES_CACHE_H
diff --git a/src/aosp/suggest/core/dicnode/internal/dic_node_properties.h b/src/aosp/suggest/core/dicnode/internal/dic_node_properties.h
new file mode 100644
index 0000000..9e0f62c
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/internal/dic_node_properties.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_PROPERTIES_H
+#define LATINIME_DIC_NODE_PROPERTIES_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+/**
+ * Node for traversing the lexicon trie.
+ */
+// TODO: Introduce a dictionary node class which has attribute members required to understand the
+// dictionary structure.
+class DicNodeProperties {
+ public:
+    AK_FORCE_INLINE DicNodeProperties()
+            : mPos(0), mChildrenPos(0), mProbability(0), mNodeCodePoint(0), mIsTerminal(false),
+              mHasChildren(false), mIsBlacklistedOrNotAWord(false), mDepth(0), mLeavingDepth(0) {}
+
+    virtual ~DicNodeProperties() {}
+
+    // Should be called only once per DicNode is initialized.
+    void init(const int pos, const int childrenPos, const int nodeCodePoint, const int probability,
+            const bool isTerminal, const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t depth, const uint16_t leavingDepth) {
+        mPos = pos;
+        mChildrenPos = childrenPos;
+        mNodeCodePoint = nodeCodePoint;
+        mProbability = probability;
+        mIsTerminal = isTerminal;
+        mHasChildren = hasChildren;
+        mIsBlacklistedOrNotAWord = isBlacklistedOrNotAWord;
+        mDepth = depth;
+        mLeavingDepth = leavingDepth;
+    }
+
+    // Init for copy
+    void init(const DicNodeProperties *const nodeProp) {
+        mPos = nodeProp->mPos;
+        mChildrenPos = nodeProp->mChildrenPos;
+        mNodeCodePoint = nodeProp->mNodeCodePoint;
+        mProbability = nodeProp->mProbability;
+        mIsTerminal = nodeProp->mIsTerminal;
+        mHasChildren = nodeProp->mHasChildren;
+        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
+        mDepth = nodeProp->mDepth;
+        mLeavingDepth = nodeProp->mLeavingDepth;
+    }
+
+    // Init as passing child
+    void init(const DicNodeProperties *const nodeProp, const int codePoint) {
+        mPos = nodeProp->mPos;
+        mChildrenPos = nodeProp->mChildrenPos;
+        mNodeCodePoint = codePoint; // Overwrite the node char of a passing child
+        mProbability = nodeProp->mProbability;
+        mIsTerminal = nodeProp->mIsTerminal;
+        mHasChildren = nodeProp->mHasChildren;
+        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
+        mDepth = nodeProp->mDepth + 1; // Increment the depth of a passing child
+        mLeavingDepth = nodeProp->mLeavingDepth;
+    }
+
+    int getPos() const {
+        return mPos;
+    }
+
+    int getChildrenPos() const {
+        return mChildrenPos;
+    }
+
+    int getProbability() const {
+        return mProbability;
+    }
+
+    int getNodeCodePoint() const {
+        return mNodeCodePoint;
+    }
+
+    uint16_t getDepth() const {
+        return mDepth;
+    }
+
+    // TODO: Move to output?
+    uint16_t getLeavingDepth() const {
+        return mLeavingDepth;
+    }
+
+    bool isTerminal() const {
+        return mIsTerminal;
+    }
+
+    bool hasChildren() const {
+        return mHasChildren || mDepth != mLeavingDepth;
+    }
+
+    bool isBlacklistedOrNotAWord() const {
+        return mIsBlacklistedOrNotAWord;
+    }
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+    int mPos;
+    int mChildrenPos;
+    int mProbability;
+    int mNodeCodePoint;
+    bool mIsTerminal;
+    bool mHasChildren;
+    bool mIsBlacklistedOrNotAWord;
+    uint16_t mDepth;
+    uint16_t mLeavingDepth;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_PROPERTIES_H
diff --git a/src/aosp/suggest/core/dicnode/internal/dic_node_state.h b/src/aosp/suggest/core/dicnode/internal/dic_node_state.h
new file mode 100644
index 0000000..b0fddb7
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/internal/dic_node_state.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_STATE_H
+#define LATINIME_DIC_NODE_STATE_H
+
+#include "defines.h"
+#include "suggest/core/dicnode/internal/dic_node_state_input.h"
+#include "suggest/core/dicnode/internal/dic_node_state_output.h"
+#include "suggest/core/dicnode/internal/dic_node_state_prevword.h"
+#include "suggest/core/dicnode/internal/dic_node_state_scoring.h"
+
+namespace latinime {
+
+class DicNodeState {
+ public:
+    DicNodeStateInput mDicNodeStateInput;
+    DicNodeStateOutput mDicNodeStateOutput;
+    DicNodeStatePrevWord mDicNodeStatePrevWord;
+    DicNodeStateScoring mDicNodeStateScoring;
+
+    AK_FORCE_INLINE DicNodeState()
+            : mDicNodeStateInput(), mDicNodeStateOutput(), mDicNodeStatePrevWord(),
+              mDicNodeStateScoring() {
+    }
+
+    virtual ~DicNodeState() {}
+
+    // Init with prevWordPos
+    void init(const int prevWordPos) {
+        mDicNodeStateInput.init();
+        mDicNodeStateOutput.init();
+        mDicNodeStatePrevWord.init(prevWordPos);
+        mDicNodeStateScoring.init();
+    }
+
+    // Init by copy
+    AK_FORCE_INLINE void init(const DicNodeState *const src) {
+        mDicNodeStateInput.init(&src->mDicNodeStateInput);
+        mDicNodeStateOutput.init(&src->mDicNodeStateOutput);
+        mDicNodeStatePrevWord.init(&src->mDicNodeStatePrevWord);
+        mDicNodeStateScoring.init(&src->mDicNodeStateScoring);
+    }
+
+    // Init by copy and adding merged node code points.
+    void init(const DicNodeState *const src, const uint16_t mergedNodeCodePointCount,
+            const int *const mergedNodeCodePoints) {
+        init(src);
+        mDicNodeStateOutput.addMergedNodeCodePoints(
+                mergedNodeCodePointCount, mergedNodeCodePoints);
+    }
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_H
diff --git a/src/aosp/suggest/core/dicnode/internal/dic_node_state_input.h b/src/aosp/suggest/core/dicnode/internal/dic_node_state_input.h
new file mode 100644
index 0000000..bbd9435
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/internal/dic_node_state_input.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_STATE_INPUT_H
+#define LATINIME_DIC_NODE_STATE_INPUT_H
+
+#include "defines.h"
+
+namespace latinime {
+
+// TODO: Have a .cpp for this class
+class DicNodeStateInput {
+ public:
+    DicNodeStateInput() {}
+    virtual ~DicNodeStateInput() {}
+
+    // TODO: Merge into DicNodeStatePrevWord::truncate
+    void truncate(const int commitPoint) {
+        mInputIndex[0] -= commitPoint;
+    }
+
+    void init() {
+        for (int i = 0; i < MAX_POINTER_COUNT_G; i++) {
+            // TODO: The initial value for mInputIndex should be -1?
+            //mInputIndex[i] = i == 0 ? 0 : -1;
+            mInputIndex[i] = 0;
+            mPrevCodePoint[i] = NOT_A_CODE_POINT;
+            mTerminalDiffCost[i] = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+        }
+    }
+
+    void init(const DicNodeStateInput *const src, const bool resetTerminalDiffCost) {
+        for (int i = 0; i < MAX_POINTER_COUNT_G; i++) {
+             mInputIndex[i] = src->mInputIndex[i];
+             mPrevCodePoint[i] = src->mPrevCodePoint[i];
+             mTerminalDiffCost[i] = resetTerminalDiffCost ?
+                     static_cast<float>(MAX_VALUE_FOR_WEIGHTING) : src->mTerminalDiffCost[i];
+         }
+    }
+
+    void updateInputIndexG(const int pointerId, const int inputIndex,
+            const int prevCodePoint, const float terminalDiffCost, const float rawLength) {
+        mInputIndex[pointerId] = inputIndex;
+        mPrevCodePoint[pointerId] = prevCodePoint;
+        mTerminalDiffCost[pointerId] = terminalDiffCost;
+    }
+
+    void init(const DicNodeStateInput *const src) {
+        init(src, false);
+    }
+
+    // For transposition
+    void setPrevCodePoint(const int pointerId, const int c) {
+        mPrevCodePoint[pointerId] = c;
+    }
+
+    void forwardInputIndex(const int pointerId, const int val) {
+        if (mInputIndex[pointerId] < 0) {
+            mInputIndex[pointerId] = val;
+        } else {
+            mInputIndex[pointerId] = mInputIndex[pointerId] + val;
+        }
+    }
+
+    int getInputIndex(const int pointerId) const {
+        // when "inputIndex" exceeds "inputSize", auto-completion needs to be done
+        return mInputIndex[pointerId];
+    }
+
+    int getPrevCodePoint(const int pointerId) const {
+        return mPrevCodePoint[pointerId];
+    }
+
+    float getTerminalDiffCost(const int pointerId) const {
+        return mTerminalDiffCost[pointerId];
+    }
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+    int mInputIndex[MAX_POINTER_COUNT_G];
+    int mPrevCodePoint[MAX_POINTER_COUNT_G];
+    float mTerminalDiffCost[MAX_POINTER_COUNT_G];
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_INPUT_H
diff --git a/src/aosp/suggest/core/dicnode/internal/dic_node_state_output.h b/src/aosp/suggest/core/dicnode/internal/dic_node_state_output.h
new file mode 100644
index 0000000..74eb5df
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/internal/dic_node_state_output.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_STATE_OUTPUT_H
+#define LATINIME_DIC_NODE_STATE_OUTPUT_H
+
+#include <cstring> // for memcpy()
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicNodeStateOutput {
+ public:
+    DicNodeStateOutput() : mOutputtedCodePointCount(0) {
+        init();
+    }
+
+    virtual ~DicNodeStateOutput() {}
+
+    void init() {
+        mOutputtedCodePointCount = 0;
+        mCodePointsBuf[0] = 0;
+    }
+
+    void init(const DicNodeStateOutput *const stateOutput) {
+        memcpy(mCodePointsBuf, stateOutput->mCodePointsBuf,
+                stateOutput->mOutputtedCodePointCount * sizeof(mCodePointsBuf[0]));
+        mOutputtedCodePointCount = stateOutput->mOutputtedCodePointCount;
+        if (mOutputtedCodePointCount < MAX_WORD_LENGTH) {
+            mCodePointsBuf[mOutputtedCodePointCount] = 0;
+        }
+    }
+
+    void addMergedNodeCodePoints(const uint16_t mergedNodeCodePointCount,
+            const int *const mergedNodeCodePoints) {
+        if (mergedNodeCodePoints) {
+            const int additionalCodePointCount = min(static_cast<int>(mergedNodeCodePointCount),
+                    MAX_WORD_LENGTH - mOutputtedCodePointCount);
+            memcpy(&mCodePointsBuf[mOutputtedCodePointCount], mergedNodeCodePoints,
+                    additionalCodePointCount * sizeof(mCodePointsBuf[0]));
+            mOutputtedCodePointCount = static_cast<uint16_t>(
+                    mOutputtedCodePointCount + mergedNodeCodePointCount);
+            if (mOutputtedCodePointCount < MAX_WORD_LENGTH) {
+                mCodePointsBuf[mOutputtedCodePointCount] = 0;
+            }
+        }
+    }
+
+    // TODO: Remove
+    int getCodePointAt(const int index) const {
+        return mCodePointsBuf[index];
+    }
+
+    // TODO: Move to private
+    int mCodePointsBuf[MAX_WORD_LENGTH];
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+    uint16_t mOutputtedCodePointCount;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_OUTPUT_H
diff --git a/src/aosp/suggest/core/dicnode/internal/dic_node_state_prevword.h b/src/aosp/suggest/core/dicnode/internal/dic_node_state_prevword.h
new file mode 100644
index 0000000..b898620
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/internal/dic_node_state_prevword.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_STATE_PREVWORD_H
+#define LATINIME_DIC_NODE_STATE_PREVWORD_H
+
+#include <cstring> // for memset()
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/layout/proximity_info_state.h"
+
+namespace latinime {
+
+class DicNodeStatePrevWord {
+ public:
+    AK_FORCE_INLINE DicNodeStatePrevWord()
+            : mPrevWordCount(0), mPrevWordLength(0), mPrevWordStart(0), mPrevWordProbability(0),
+              mPrevWordNodePos(NOT_A_DICT_POS), mSecondWordFirstInputIndex(NOT_AN_INDEX) {
+        memset(mPrevWord, 0, sizeof(mPrevWord));
+    }
+
+    virtual ~DicNodeStatePrevWord() {}
+
+    void init() {
+        mPrevWordLength = 0;
+        mPrevWordCount = 0;
+        mPrevWordStart = 0;
+        mPrevWordProbability = -1;
+        mPrevWordNodePos = NOT_A_DICT_POS;
+        mSecondWordFirstInputIndex = NOT_AN_INDEX;
+    }
+
+    void init(const int prevWordNodePos) {
+        mPrevWordLength = 0;
+        mPrevWordCount = 0;
+        mPrevWordStart = 0;
+        mPrevWordProbability = -1;
+        mPrevWordNodePos = prevWordNodePos;
+        mSecondWordFirstInputIndex = NOT_AN_INDEX;
+    }
+
+    // Init by copy
+    AK_FORCE_INLINE void init(const DicNodeStatePrevWord *const prevWord) {
+        mPrevWordLength = prevWord->mPrevWordLength;
+        mPrevWordCount = prevWord->mPrevWordCount;
+        mPrevWordStart = prevWord->mPrevWordStart;
+        mPrevWordProbability = prevWord->mPrevWordProbability;
+        mPrevWordNodePos = prevWord->mPrevWordNodePos;
+        mSecondWordFirstInputIndex = prevWord->mSecondWordFirstInputIndex;
+        memcpy(mPrevWord, prevWord->mPrevWord, prevWord->mPrevWordLength * sizeof(mPrevWord[0]));
+    }
+
+    void init(const int16_t prevWordCount, const int16_t prevWordProbability,
+            const int prevWordNodePos, const int *const src0, const int16_t length0,
+            const int *const src1, const int16_t length1,
+            const int prevWordSecondWordFirstInputIndex, const int lastInputIndex) {
+        mPrevWordCount = min(prevWordCount, static_cast<int16_t>(MAX_RESULTS));
+        mPrevWordProbability = prevWordProbability;
+        mPrevWordNodePos = prevWordNodePos;
+        int twoWordsLen =
+                DicNodeUtils::appendTwoWords(src0, length0, src1, length1, mPrevWord);
+        if (twoWordsLen >= MAX_WORD_LENGTH) {
+            twoWordsLen = MAX_WORD_LENGTH - 1;
+        }
+        mPrevWord[twoWordsLen] = KEYCODE_SPACE;
+        mPrevWordStart = length0;
+        mPrevWordLength = static_cast<int16_t>(twoWordsLen + 1);
+        mSecondWordFirstInputIndex = prevWordSecondWordFirstInputIndex;
+    }
+
+    void truncate(const int offset) {
+        // TODO: memmove
+        if (mPrevWordLength < offset) {
+            memset(mPrevWord, 0, sizeof(mPrevWord));
+            mPrevWordLength = 0;
+            return;
+        }
+        const int newPrevWordLength = mPrevWordLength - offset;
+        memmove(mPrevWord, &mPrevWord[offset], newPrevWordLength * sizeof(mPrevWord[0]));
+        mPrevWordLength = newPrevWordLength;
+    }
+
+    void setSecondWordFirstInputIndex(const int inputIndex) {
+        mSecondWordFirstInputIndex = inputIndex;
+    }
+
+    int getSecondWordFirstInputIndex() const {
+        return mSecondWordFirstInputIndex;
+    }
+
+    // TODO: remove
+    int16_t getPrevWordLength() const {
+        return mPrevWordLength;
+    }
+
+    int16_t getPrevWordCount() const {
+        return mPrevWordCount;
+    }
+
+    int16_t getPrevWordStart() const {
+        return mPrevWordStart;
+    }
+
+    int getPrevWordNodePos() const {
+        return mPrevWordNodePos;
+    }
+
+    int getPrevWordCodePointAt(const int id) const {
+        return mPrevWord[id];
+    }
+
+    bool startsWith(const DicNodeStatePrevWord *const prefix, const int prefixLen) const {
+        if (prefixLen > mPrevWordLength) {
+            return false;
+        }
+        for (int i = 0; i < prefixLen; ++i) {
+            if (mPrevWord[i] != prefix->mPrevWord[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // TODO: Move to private
+    int mPrevWord[MAX_WORD_LENGTH];
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+    int16_t mPrevWordCount;
+    int16_t mPrevWordLength;
+    int16_t mPrevWordStart;
+    int16_t mPrevWordProbability;
+    int mPrevWordNodePos;
+    int mSecondWordFirstInputIndex;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_PREVWORD_H
diff --git a/src/aosp/suggest/core/dicnode/internal/dic_node_state_scoring.h b/src/aosp/suggest/core/dicnode/internal/dic_node_state_scoring.h
new file mode 100644
index 0000000..3c85d0e
--- /dev/null
+++ b/src/aosp/suggest/core/dicnode/internal/dic_node_state_scoring.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_STATE_SCORING_H
+#define LATINIME_DIC_NODE_STATE_SCORING_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/digraph_utils.h"
+
+namespace latinime {
+
+class DicNodeStateScoring {
+ public:
+    AK_FORCE_INLINE DicNodeStateScoring()
+            : mDoubleLetterLevel(NOT_A_DOUBLE_LETTER),
+              mDigraphIndex(DigraphUtils::NOT_A_DIGRAPH_INDEX),
+              mEditCorrectionCount(0), mProximityCorrectionCount(0),
+              mNormalizedCompoundDistance(0.0f), mSpatialDistance(0.0f), mLanguageDistance(0.0f),
+              mRawLength(0.0f), mExactMatch(true),
+              mNormalizedCompoundDistanceAfterFirstWord(MAX_VALUE_FOR_WEIGHTING) {
+    }
+
+    virtual ~DicNodeStateScoring() {}
+
+    void init() {
+        mEditCorrectionCount = 0;
+        mProximityCorrectionCount = 0;
+        mNormalizedCompoundDistance = 0.0f;
+        mSpatialDistance = 0.0f;
+        mLanguageDistance = 0.0f;
+        mRawLength = 0.0f;
+        mDoubleLetterLevel = NOT_A_DOUBLE_LETTER;
+        mDigraphIndex = DigraphUtils::NOT_A_DIGRAPH_INDEX;
+        mNormalizedCompoundDistanceAfterFirstWord = MAX_VALUE_FOR_WEIGHTING;
+        mExactMatch = true;
+    }
+
+    AK_FORCE_INLINE void init(const DicNodeStateScoring *const scoring) {
+        mEditCorrectionCount = scoring->mEditCorrectionCount;
+        mProximityCorrectionCount = scoring->mProximityCorrectionCount;
+        mNormalizedCompoundDistance = scoring->mNormalizedCompoundDistance;
+        mSpatialDistance = scoring->mSpatialDistance;
+        mLanguageDistance = scoring->mLanguageDistance;
+        mRawLength = scoring->mRawLength;
+        mDoubleLetterLevel = scoring->mDoubleLetterLevel;
+        mDigraphIndex = scoring->mDigraphIndex;
+        mExactMatch = scoring->mExactMatch;
+        mNormalizedCompoundDistanceAfterFirstWord =
+                scoring->mNormalizedCompoundDistanceAfterFirstWord;
+    }
+
+    void addCost(const float spatialCost, const float languageCost, const bool doNormalization,
+            const int inputSize, const int totalInputIndex, const ErrorType errorType) {
+        addDistance(spatialCost, languageCost, doNormalization, inputSize, totalInputIndex);
+        switch (errorType) {
+            case ET_EDIT_CORRECTION:
+                ++mEditCorrectionCount;
+                mExactMatch = false;
+                break;
+            case ET_PROXIMITY_CORRECTION:
+                ++mProximityCorrectionCount;
+                mExactMatch = false;
+                break;
+            case ET_COMPLETION:
+                mExactMatch = false;
+                break;
+            case ET_NEW_WORD:
+                mExactMatch = false;
+                break;
+            case ET_INTENTIONAL_OMISSION:
+                mExactMatch = false;
+                break;
+            case ET_NOT_AN_ERROR:
+                break;
+        }
+    }
+
+    // Saves the current normalized distance for space-aware gestures.
+    // See getNormalizedCompoundDistanceAfterFirstWord for details.
+    void saveNormalizedCompoundDistanceAfterFirstWordIfNoneYet() {
+        // We get called here after each word. We only want to store the distance after
+        // the first word, so if we already have a distance we skip saving -- hence "IfNoneYet"
+        // in the method name.
+        if (mNormalizedCompoundDistanceAfterFirstWord >= MAX_VALUE_FOR_WEIGHTING) {
+            mNormalizedCompoundDistanceAfterFirstWord = getNormalizedCompoundDistance();
+        }
+    }
+
+    void addRawLength(const float rawLength) {
+        mRawLength += rawLength;
+    }
+
+    float getCompoundDistance() const {
+        return getCompoundDistance(1.0f);
+    }
+
+    float getCompoundDistance(const float languageWeight) const {
+        return mSpatialDistance + mLanguageDistance * languageWeight;
+    }
+
+    float getNormalizedCompoundDistance() const {
+        return mNormalizedCompoundDistance;
+    }
+
+    // For space-aware gestures, we store the normalized distance at the char index
+    // that ends the first word of the suggestion. We call this the distance after
+    // first word.
+    float getNormalizedCompoundDistanceAfterFirstWord() const {
+        return mNormalizedCompoundDistanceAfterFirstWord;
+    }
+
+    float getSpatialDistance() const {
+        return mSpatialDistance;
+    }
+
+    float getLanguageDistance() const {
+        return mLanguageDistance;
+    }
+
+    int16_t getEditCorrectionCount() const {
+        return mEditCorrectionCount;
+    }
+
+    int16_t getProximityCorrectionCount() const {
+        return mProximityCorrectionCount;
+    }
+
+    float getRawLength() const {
+        return mRawLength;
+    }
+
+    DoubleLetterLevel getDoubleLetterLevel() const {
+        return mDoubleLetterLevel;
+    }
+
+    void setDoubleLetterLevel(DoubleLetterLevel doubleLetterLevel) {
+        switch(doubleLetterLevel) {
+            case NOT_A_DOUBLE_LETTER:
+                break;
+            case A_DOUBLE_LETTER:
+                if (mDoubleLetterLevel != A_STRONG_DOUBLE_LETTER) {
+                    mDoubleLetterLevel = doubleLetterLevel;
+                }
+                break;
+            case A_STRONG_DOUBLE_LETTER:
+                mDoubleLetterLevel = doubleLetterLevel;
+                break;
+        }
+    }
+
+    DigraphUtils::DigraphCodePointIndex getDigraphIndex() const {
+        return mDigraphIndex;
+    }
+
+    void advanceDigraphIndex() {
+        switch(mDigraphIndex) {
+            case DigraphUtils::NOT_A_DIGRAPH_INDEX:
+                mDigraphIndex = DigraphUtils::FIRST_DIGRAPH_CODEPOINT;
+                break;
+            case DigraphUtils::FIRST_DIGRAPH_CODEPOINT:
+                mDigraphIndex = DigraphUtils::SECOND_DIGRAPH_CODEPOINT;
+                break;
+            case DigraphUtils::SECOND_DIGRAPH_CODEPOINT:
+                mDigraphIndex = DigraphUtils::NOT_A_DIGRAPH_INDEX;
+                break;
+        }
+    }
+
+    bool isExactMatch() const {
+        return mExactMatch;
+    }
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+    DoubleLetterLevel mDoubleLetterLevel;
+    DigraphUtils::DigraphCodePointIndex mDigraphIndex;
+
+    int16_t mEditCorrectionCount;
+    int16_t mProximityCorrectionCount;
+
+    float mNormalizedCompoundDistance;
+    float mSpatialDistance;
+    float mLanguageDistance;
+    float mRawLength;
+    bool mExactMatch;
+    float mNormalizedCompoundDistanceAfterFirstWord;
+
+    AK_FORCE_INLINE void addDistance(float spatialDistance, float languageDistance,
+            bool doNormalization, int inputSize, int totalInputIndex) {
+        mSpatialDistance += spatialDistance;
+        mLanguageDistance += languageDistance;
+        if (!doNormalization) {
+            mNormalizedCompoundDistance = mSpatialDistance + mLanguageDistance;
+        } else {
+            mNormalizedCompoundDistance = (mSpatialDistance + mLanguageDistance)
+                    / static_cast<float>(max(1, totalInputIndex));
+        }
+    }
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_SCORING_H
diff --git a/src/aosp/suggest/core/dictionary/bigram_dictionary.cpp b/src/aosp/suggest/core/dictionary/bigram_dictionary.cpp
new file mode 100644
index 0000000..71f4ef6
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/bigram_dictionary.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010, 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 <cstring>
+
+#define LOG_TAG "LatinIME: bigram_dictionary.cpp"
+
+#include "bigram_dictionary.h"
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "utils/char_utils.h"
+
+namespace latinime {
+
+BigramDictionary::BigramDictionary(
+        const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy)
+        : mDictionaryStructurePolicy(dictionaryStructurePolicy) {
+    if (DEBUG_DICT) {
+        AKLOGI("BigramDictionary - constructor");
+    }
+}
+
+BigramDictionary::~BigramDictionary() {
+}
+
+void BigramDictionary::addWordBigram(int *word, int length, int probability, int *bigramProbability,
+        int *bigramCodePoints, int *outputTypes) const {
+    word[length] = 0;
+    if (DEBUG_DICT_FULL) {
+#ifdef FLAG_DBG
+        char s[length + 1];
+        for (int i = 0; i <= length; i++) s[i] = static_cast<char>(word[i]);
+        AKLOGI("Bigram: Found word = %s, freq = %d :", s, probability);
+#endif
+    }
+
+    // Find the right insertion point
+    int insertAt = 0;
+    while (insertAt < MAX_RESULTS) {
+        if (probability > bigramProbability[insertAt] || (bigramProbability[insertAt] == probability
+                && length < CharUtils::getCodePointCount(MAX_WORD_LENGTH,
+                        bigramCodePoints + insertAt * MAX_WORD_LENGTH))) {
+            break;
+        }
+        insertAt++;
+    }
+    if (DEBUG_DICT_FULL) {
+        AKLOGI("Bigram: InsertAt -> %d MAX_RESULTS: %d", insertAt, MAX_RESULTS);
+    }
+    if (insertAt >= MAX_RESULTS) {
+        return;
+    }
+    memmove(bigramProbability + (insertAt + 1),
+            bigramProbability + insertAt,
+            (MAX_RESULTS - insertAt - 1) * sizeof(bigramProbability[0]));
+    bigramProbability[insertAt] = probability;
+    outputTypes[insertAt] = Dictionary::KIND_PREDICTION;
+    memmove(bigramCodePoints + (insertAt + 1) * MAX_WORD_LENGTH,
+            bigramCodePoints + insertAt * MAX_WORD_LENGTH,
+            (MAX_RESULTS - insertAt - 1) * sizeof(bigramCodePoints[0]) * MAX_WORD_LENGTH);
+    int *dest = bigramCodePoints + insertAt * MAX_WORD_LENGTH;
+    while (length--) {
+        *dest++ = *word++;
+    }
+    *dest = 0; // NULL terminate
+    if (DEBUG_DICT_FULL) {
+        AKLOGI("Bigram: Added word at %d", insertAt);
+    }
+}
+
+/* Parameters :
+ * prevWord: the word before, the one for which we need to look up bigrams.
+ * prevWordLength: its length.
+ * outBigramCodePoints: an array for output, at the same format as outwords for getSuggestions.
+ * outBigramProbability: an array to output frequencies.
+ * outputTypes: an array to output types.
+ * This method returns the number of bigrams this word has, for backward compatibility.
+ */
+int BigramDictionary::getPredictions(const int *prevWord, const int prevWordLength,
+        int *const outBigramCodePoints, int *const outBigramProbability,
+        int *const outputTypes) const {
+    // TODO: remove unused arguments, and refrain from storing stuff in members of this class
+    // TODO: have "in" arguments before "out" ones, and make out args explicit in the name
+
+    int pos = getBigramListPositionForWord(prevWord, prevWordLength,
+            false /* forceLowerCaseSearch */);
+    // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
+    if (NOT_A_DICT_POS == pos) {
+        // If no bigrams for this exact word, search again in lower case.
+        pos = getBigramListPositionForWord(prevWord, prevWordLength,
+                true /* forceLowerCaseSearch */);
+    }
+    // If still no bigrams, we really don't have them!
+    if (NOT_A_DICT_POS == pos) return 0;
+
+    int bigramCount = 0;
+    int unigramProbability = 0;
+    int bigramBuffer[MAX_WORD_LENGTH];
+    BinaryDictionaryBigramsIterator bigramsIt(
+            mDictionaryStructurePolicy->getBigramsStructurePolicy(), pos);
+    while (bigramsIt.hasNext()) {
+        bigramsIt.next();
+        if (bigramsIt.getBigramPos() == NOT_A_DICT_POS) {
+            continue;
+        }
+        const int codePointCount = mDictionaryStructurePolicy->
+                getCodePointsAndProbabilityAndReturnCodePointCount(bigramsIt.getBigramPos(),
+                        MAX_WORD_LENGTH, bigramBuffer, &unigramProbability);
+        if (codePointCount <= 0) {
+            continue;
+        }
+        // Due to space constraints, the probability for bigrams is approximate - the lower the
+        // unigram probability, the worse the precision. The theoritical maximum error in
+        // resulting probability is 8 - although in the practice it's never bigger than 3 or 4
+        // in very bad cases. This means that sometimes, we'll see some bigrams interverted
+        // here, but it can't get too bad.
+        const int probability = mDictionaryStructurePolicy->getProbability(
+                unigramProbability, bigramsIt.getProbability());
+        addWordBigram(bigramBuffer, codePointCount, probability, outBigramProbability,
+                outBigramCodePoints, outputTypes);
+        ++bigramCount;
+    }
+    return min(bigramCount, MAX_RESULTS);
+}
+
+// Returns a pointer to the start of the bigram list.
+// If the word is not found or has no bigrams, this function returns NOT_A_DICT_POS.
+int BigramDictionary::getBigramListPositionForWord(const int *prevWord, const int prevWordLength,
+        const bool forceLowerCaseSearch) const {
+    if (0 >= prevWordLength) return NOT_A_DICT_POS;
+    int pos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(prevWord, prevWordLength,
+            forceLowerCaseSearch);
+    if (NOT_A_DICT_POS == pos) return NOT_A_DICT_POS;
+    return mDictionaryStructurePolicy->getBigramsPositionOfPtNode(pos);
+}
+
+int BigramDictionary::getBigramProbability(const int *word0, int length0, const int *word1,
+        int length1) const {
+    int pos = getBigramListPositionForWord(word0, length0, false /* forceLowerCaseSearch */);
+    // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
+    if (NOT_A_DICT_POS == pos) return NOT_A_PROBABILITY;
+    int nextWordPos = mDictionaryStructurePolicy->getTerminalNodePositionOfWord(word1, length1,
+            false /* forceLowerCaseSearch */);
+    if (NOT_A_DICT_POS == nextWordPos) return NOT_A_PROBABILITY;
+
+    BinaryDictionaryBigramsIterator bigramsIt(
+            mDictionaryStructurePolicy->getBigramsStructurePolicy(), pos);
+    while (bigramsIt.hasNext()) {
+        bigramsIt.next();
+        if (bigramsIt.getBigramPos() == nextWordPos) {
+            return mDictionaryStructurePolicy->getProbability(
+                    mDictionaryStructurePolicy->getUnigramProbabilityOfPtNode(nextWordPos),
+                    bigramsIt.getProbability());
+        }
+    }
+    return NOT_A_PROBABILITY;
+}
+
+// TODO: Move functions related to bigram to here
+} // namespace latinime
diff --git a/src/aosp/suggest/core/dictionary/bigram_dictionary.h b/src/aosp/suggest/core/dictionary/bigram_dictionary.h
new file mode 100644
index 0000000..8af7ee7
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/bigram_dictionary.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 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_BIGRAM_DICTIONARY_H
+#define LATINIME_BIGRAM_DICTIONARY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DictionaryStructureWithBufferPolicy;
+
+class BigramDictionary {
+ public:
+    BigramDictionary(const DictionaryStructureWithBufferPolicy *const dictionaryStructurePolicy);
+
+    int getPredictions(const int *word, int length, int *outBigramCodePoints,
+            int *outBigramProbability, int *outputTypes) const;
+    int getBigramProbability(const int *word1, int length1, const int *word2, int length2) const;
+    ~BigramDictionary();
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BigramDictionary);
+
+    void addWordBigram(int *word, int length, int probability, int *bigramProbability,
+            int *bigramCodePoints, int *outputTypes) const;
+    int getBigramListPositionForWord(const int *prevWord, const int prevWordLength,
+            const bool forceLowerCaseSearch) const;
+
+    const DictionaryStructureWithBufferPolicy *const mDictionaryStructurePolicy;
+};
+} // namespace latinime
+#endif // LATINIME_BIGRAM_DICTIONARY_H
diff --git a/src/aosp/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h b/src/aosp/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h
new file mode 100644
index 0000000..d16ac47
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h
@@ -0,0 +1,59 @@
+/*
+ * 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_BINARY_DICTIONARY_BIGRAMS_ITERATOR_H
+#define LATINIME_BINARY_DICTIONARY_BIGRAMS_ITERATOR_H
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+
+namespace latinime {
+
+class BinaryDictionaryBigramsIterator {
+ public:
+    BinaryDictionaryBigramsIterator(
+            const DictionaryBigramsStructurePolicy *const bigramsStructurePolicy, const int pos)
+            : mBigramsStructurePolicy(bigramsStructurePolicy), mPos(pos),
+              mBigramPos(NOT_A_DICT_POS), mProbability(NOT_A_PROBABILITY),
+              mHasNext(pos != NOT_A_DICT_POS) {}
+
+    AK_FORCE_INLINE bool hasNext() const {
+        return mHasNext;
+    }
+
+    AK_FORCE_INLINE void next() {
+        mBigramsStructurePolicy->getNextBigram(&mBigramPos, &mProbability, &mHasNext, &mPos);
+    }
+
+    AK_FORCE_INLINE int getProbability() const {
+        return mProbability;
+    }
+
+    AK_FORCE_INLINE int getBigramPos() const {
+        return mBigramPos;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryBigramsIterator);
+
+    const DictionaryBigramsStructurePolicy *const mBigramsStructurePolicy;
+    int mPos;
+    int mBigramPos;
+    int mProbability;
+    bool mHasNext;
+};
+} // namespace latinime
+#endif // LATINIME_BINARY_DICTIONARY_BIGRAMS_ITERATOR_H
diff --git a/src/aosp/suggest/core/dictionary/binary_dictionary_shortcut_iterator.h b/src/aosp/suggest/core/dictionary/binary_dictionary_shortcut_iterator.h
new file mode 100644
index 0000000..558e0a5
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/binary_dictionary_shortcut_iterator.h
@@ -0,0 +1,55 @@
+/*
+ * 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_BINARY_DICTIONARY_SHORTCUT_ITERATOR_H
+#define LATINIME_BINARY_DICTIONARY_SHORTCUT_ITERATOR_H
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+
+namespace latinime {
+
+class BinaryDictionaryShortcutIterator {
+ public:
+    BinaryDictionaryShortcutIterator(
+            const DictionaryShortcutsStructurePolicy *const shortcutStructurePolicy,
+            const int shortcutPos)
+            : mShortcutStructurePolicy(shortcutStructurePolicy),
+              mPos(shortcutStructurePolicy->getStartPos(shortcutPos)),
+              mHasNextShortcutTarget(shortcutPos != NOT_A_DICT_POS) {}
+
+    AK_FORCE_INLINE bool hasNextShortcutTarget() const {
+        return mHasNextShortcutTarget;
+    }
+
+    // Gets the shortcut target itself as an int string and put it to outTarget, put its length
+    // to outTargetLength, put whether it is whitelist to outIsWhitelist.
+    AK_FORCE_INLINE void nextShortcutTarget(
+            const int maxDepth, int *const outTarget, int *const outTargetLength,
+            bool *const outIsWhitelist) {
+        mShortcutStructurePolicy->getNextShortcut(maxDepth, outTarget, outTargetLength,
+                outIsWhitelist, &mHasNextShortcutTarget, &mPos);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryShortcutIterator);
+
+    const DictionaryShortcutsStructurePolicy *const mShortcutStructurePolicy;
+    int mPos;
+    bool mHasNextShortcutTarget;
+};
+} // namespace latinime
+#endif // LATINIME_BINARY_DICTIONARY_SHORTCUT_ITERATOR_H
diff --git a/src/aosp/suggest/core/dictionary/bloom_filter.cpp b/src/aosp/suggest/core/dictionary/bloom_filter.cpp
new file mode 100644
index 0000000..4ae474e
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/bloom_filter.cpp
@@ -0,0 +1,25 @@
+/*
+ * 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/core/dictionary/bloom_filter.h"
+
+namespace latinime {
+
+// Must be smaller than BIGRAM_FILTER_BYTE_SIZE * 8, and preferably prime. 1021 is the largest
+// prime under 128 * 8.
+const int BloomFilter::BIGRAM_FILTER_MODULO = 1021;
+
+} // namespace latinime
diff --git a/src/aosp/suggest/core/dictionary/bloom_filter.h b/src/aosp/suggest/core/dictionary/bloom_filter.h
new file mode 100644
index 0000000..5205456
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/bloom_filter.h
@@ -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.
+ */
+
+#ifndef LATINIME_BLOOM_FILTER_H
+#define LATINIME_BLOOM_FILTER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+// This bloom filter is used for optimizing bigram retrieval.
+// Execution times with previous word "this" are as follows:
+//  without bloom filter (use only hash_map):
+//   Total 147792.34 (sum of others 147771.57)
+//  with bloom filter:
+//   Total 145900.64 (sum of others 145874.30)
+//  always read binary dictionary:
+//   Total 148603.14 (sum of others 148579.90)
+class BloomFilter {
+ public:
+    BloomFilter() {
+        ASSERT(BIGRAM_FILTER_BYTE_SIZE * 8 >= BIGRAM_FILTER_MODULO);
+    }
+
+    // TODO: uint32_t position
+    AK_FORCE_INLINE void setInFilter(const int32_t position) {
+        const uint32_t bucket = static_cast<uint32_t>(position % BIGRAM_FILTER_MODULO);
+        mFilter[bucket >> 3] |= static_cast<uint8_t>(1 << (bucket & 0x7));
+    }
+
+    // TODO: uint32_t position
+    AK_FORCE_INLINE bool isInFilter(const int32_t position) const {
+        const uint32_t bucket = static_cast<uint32_t>(position % BIGRAM_FILTER_MODULO);
+        return (mFilter[bucket >> 3] & static_cast<uint8_t>(1 << (bucket & 0x7))) != 0;
+    }
+
+ private:
+    // Size, in bytes, of the bloom filter index for bigrams
+    // 128 gives us 1024 buckets. The probability of false positive is (1 - e ** (-kn/m))**k,
+    // where k is the number of hash functions, n the number of bigrams, and m the number of
+    // bits we can test.
+    // At the moment 100 is the maximum number of bigrams for a word with the current
+    // dictionaries, so n = 100. 1024 buckets give us m = 1024.
+    // With 1 hash function, our false positive rate is about 9.3%, which should be enough for
+    // our uses since we are only using this to increase average performance. For the record,
+    // k = 2 gives 3.1% and k = 3 gives 1.6%. With k = 1, making m = 2048 gives 4.8%,
+    // and m = 4096 gives 2.4%.
+    // This is assigned here because it is used for array size.
+    static const int BIGRAM_FILTER_BYTE_SIZE = 128;
+    static const int BIGRAM_FILTER_MODULO;
+
+    uint8_t mFilter[BIGRAM_FILTER_BYTE_SIZE];
+};
+} // namespace latinime
+#endif // LATINIME_BLOOM_FILTER_H
diff --git a/src/aosp/suggest/core/dictionary/dictionary.cpp b/src/aosp/suggest/core/dictionary/dictionary.cpp
new file mode 100644
index 0000000..6bc4b30
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/dictionary.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "LatinIME: dictionary.cpp"
+
+#include "suggest/core/dictionary/dictionary.h"
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/bigram_dictionary.h"
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "suggest/core/session/dic_traverse_session.h"
+#include "suggest/core/suggest.h"
+#include "suggest/core/suggest_options.h"
+#include "suggest/policyimpl/gesture/gesture_suggest_policy_factory.h"
+#include "suggest/policyimpl/typing/typing_suggest_policy_factory.h"
+#include "utils/log_utils.h"
+
+namespace latinime {
+
+const int Dictionary::HEADER_ATTRIBUTE_BUFFER_SIZE = 32;
+
+Dictionary::Dictionary(JNIEnv *env,
+        DictionaryStructureWithBufferPolicy *const dictionaryStructureWithBufferPolicy)
+        : mDictionaryStructureWithBufferPolicy(dictionaryStructureWithBufferPolicy),
+          mBigramDictionary(new BigramDictionary(mDictionaryStructureWithBufferPolicy)),
+          mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
+          mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) {
+    logDictionaryInfo(env);
+}
+
+Dictionary::~Dictionary() {
+    delete mBigramDictionary;
+    delete mGestureSuggest;
+    delete mTypingSuggest;
+    delete mDictionaryStructureWithBufferPolicy;
+}
+
+int Dictionary::getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
+        int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
+        int inputSize, int *prevWordCodePoints, int prevWordLength, int commitPoint,
+        const SuggestOptions *const suggestOptions, int *outWords, int *frequencies,
+        int *spaceIndices, int *outputTypes, int *outputAutoCommitFirstWordConfidence) const {
+    int result = 0;
+    AKLOGI("HELLO! [DK]");
+    if (suggestOptions->isGesture()) {
+        DicTraverseSession::initSessionInstance(
+                traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
+        result = mGestureSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+                ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint, outWords,
+                frequencies, spaceIndices, outputTypes, outputAutoCommitFirstWordConfidence);
+        if (DEBUG_DICT) {
+            DUMP_RESULT(outWords, frequencies);
+        }
+        return result;
+    } else {
+        DicTraverseSession::initSessionInstance(
+                traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
+        result = mTypingSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+                ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint,
+                outWords, frequencies, spaceIndices, outputTypes,
+                outputAutoCommitFirstWordConfidence);
+        if (DEBUG_DICT) {
+            DUMP_RESULT(outWords, frequencies);
+        }
+        return result;
+    }
+}
+
+int Dictionary::getBigrams(const int *word, int length, int *outWords, int *frequencies,
+        int *outputTypes) const {
+    if (length <= 0) return 0;
+    return mBigramDictionary->getPredictions(word, length, outWords, frequencies, outputTypes);
+}
+
+int Dictionary::getProbability(const int *word, int length) const {
+    int pos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(word, length,
+            false /* forceLowerCaseSearch */);
+    if (NOT_A_DICT_POS == pos) {
+        return NOT_A_PROBABILITY;
+    }
+    return getDictionaryStructurePolicy()->getUnigramProbabilityOfPtNode(pos);
+}
+
+int Dictionary::getBigramProbability(const int *word0, int length0, const int *word1,
+        int length1) const {
+    return mBigramDictionary->getBigramProbability(word0, length0, word1, length1);
+}
+
+void Dictionary::addUnigramWord(const int *const word, const int length, const int probability) {
+    mDictionaryStructureWithBufferPolicy->addUnigramWord(word, length, probability);
+}
+
+void Dictionary::addBigramWords(const int *const word0, const int length0, const int *const word1,
+        const int length1, const int probability) {
+    mDictionaryStructureWithBufferPolicy->addBigramWords(word0, length0, word1, length1,
+            probability);
+}
+
+void Dictionary::removeBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1) {
+    mDictionaryStructureWithBufferPolicy->removeBigramWords(word0, length0, word1, length1);
+}
+
+void Dictionary::flush(const char *const filePath) {
+    mDictionaryStructureWithBufferPolicy->flush(filePath);
+}
+
+void Dictionary::flushWithGC(const char *const filePath) {
+    mDictionaryStructureWithBufferPolicy->flushWithGC(filePath);
+}
+
+bool Dictionary::needsToRunGC(const bool mindsBlockByGC) {
+    return mDictionaryStructureWithBufferPolicy->needsToRunGC(mindsBlockByGC);
+}
+
+void Dictionary::getProperty(const char *const query, char *const outResult,
+        const int maxResultLength) {
+    return mDictionaryStructureWithBufferPolicy->getProperty(query, outResult, maxResultLength);
+}
+
+void Dictionary::logDictionaryInfo(JNIEnv *const env) const {
+    int dictionaryIdCodePointBuffer[HEADER_ATTRIBUTE_BUFFER_SIZE];
+    int versionStringCodePointBuffer[HEADER_ATTRIBUTE_BUFFER_SIZE];
+    int dateStringCodePointBuffer[HEADER_ATTRIBUTE_BUFFER_SIZE];
+    const DictionaryHeaderStructurePolicy *const headerPolicy =
+            getDictionaryStructurePolicy()->getHeaderStructurePolicy();
+    headerPolicy->readHeaderValueOrQuestionMark("dictionary", dictionaryIdCodePointBuffer,
+            HEADER_ATTRIBUTE_BUFFER_SIZE);
+    headerPolicy->readHeaderValueOrQuestionMark("version", versionStringCodePointBuffer,
+            HEADER_ATTRIBUTE_BUFFER_SIZE);
+    headerPolicy->readHeaderValueOrQuestionMark("date", dateStringCodePointBuffer,
+            HEADER_ATTRIBUTE_BUFFER_SIZE);
+
+    char dictionaryIdCharBuffer[HEADER_ATTRIBUTE_BUFFER_SIZE];
+    char versionStringCharBuffer[HEADER_ATTRIBUTE_BUFFER_SIZE];
+    char dateStringCharBuffer[HEADER_ATTRIBUTE_BUFFER_SIZE];
+    intArrayToCharArray(dictionaryIdCodePointBuffer, HEADER_ATTRIBUTE_BUFFER_SIZE,
+            dictionaryIdCharBuffer, HEADER_ATTRIBUTE_BUFFER_SIZE);
+    intArrayToCharArray(versionStringCodePointBuffer, HEADER_ATTRIBUTE_BUFFER_SIZE,
+            versionStringCharBuffer, HEADER_ATTRIBUTE_BUFFER_SIZE);
+    intArrayToCharArray(dateStringCodePointBuffer, HEADER_ATTRIBUTE_BUFFER_SIZE,
+            dateStringCharBuffer, HEADER_ATTRIBUTE_BUFFER_SIZE);
+
+    LogUtils::logToJava(env,
+            "Dictionary info: dictionary = %s ; version = %s ; date = %s",
+            dictionaryIdCharBuffer, versionStringCharBuffer, dateStringCharBuffer);
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/core/dictionary/dictionary.h b/src/aosp/suggest/core/dictionary/dictionary.h
new file mode 100644
index 0000000..0195d5b
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/dictionary.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2009 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_DICTIONARY_H
+#define LATINIME_DICTIONARY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "jni.h"
+
+namespace latinime {
+
+class BigramDictionary;
+class DictionaryStructureWithBufferPolicy;
+class DicTraverseSession;
+class ProximityInfo;
+class SuggestInterface;
+class SuggestOptions;
+
+class Dictionary {
+ public:
+    // Taken from SuggestedWords.java
+    static const int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
+    static const int KIND_TYPED = 0; // What user typed
+    static const int KIND_CORRECTION = 1; // Simple correction/suggestion
+    static const int KIND_COMPLETION = 2; // Completion (suggestion with appended chars)
+    static const int KIND_WHITELIST = 3; // Whitelisted word
+    static const int KIND_BLACKLIST = 4; // Blacklisted word
+    static const int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation
+    static const int KIND_APP_DEFINED = 6; // Suggested by the application
+    static const int KIND_SHORTCUT = 7; // A shortcut
+    static const int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
+    // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only
+    // in java for re-correction)
+    static const int KIND_RESUMED = 9;
+    static const int KIND_OOV_CORRECTION = 10; // Most probable string correction
+
+    static const int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags
+    static const int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
+    static const int KIND_FLAG_EXACT_MATCH = 0x40000000;
+
+    Dictionary(JNIEnv *env,
+            DictionaryStructureWithBufferPolicy *const dictionaryStructureWithBufferPoilcy);
+
+    int getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
+            int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
+            int inputSize, int *prevWordCodePoints, int prevWordLength, int commitPoint,
+            const SuggestOptions *const suggestOptions, int *outWords, int *frequencies,
+            int *spaceIndices, int *outputTypes, int *outputAutoCommitFirstWordConfidence) const;
+
+    int getBigrams(const int *word, int length, int *outWords, int *frequencies,
+            int *outputTypes) const;
+
+    int getProbability(const int *word, int length) const;
+
+    int getBigramProbability(const int *word0, int length0, const int *word1, int length1) const;
+
+    void addUnigramWord(const int *const word, const int length, const int probability);
+
+    void addBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1, const int probability);
+
+    void removeBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1);
+
+    void flush(const char *const filePath);
+
+    void flushWithGC(const char *const filePath);
+
+    bool needsToRunGC(const bool mindsBlockByGC);
+
+    void getProperty(const char *const query, char *const outResult,
+            const int maxResultLength);
+
+    const DictionaryStructureWithBufferPolicy *getDictionaryStructurePolicy() const {
+        return mDictionaryStructureWithBufferPolicy;
+    }
+
+    virtual ~Dictionary();
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
+
+    static const int HEADER_ATTRIBUTE_BUFFER_SIZE;
+
+    DictionaryStructureWithBufferPolicy *const mDictionaryStructureWithBufferPolicy;
+    const BigramDictionary *const mBigramDictionary;
+    const SuggestInterface *const mGestureSuggest;
+    const SuggestInterface *const mTypingSuggest;
+
+    void logDictionaryInfo(JNIEnv *const env) const;
+};
+} // namespace latinime
+#endif // LATINIME_DICTIONARY_H
diff --git a/src/aosp/suggest/core/dictionary/digraph_utils.cpp b/src/aosp/suggest/core/dictionary/digraph_utils.cpp
new file mode 100644
index 0000000..3271c1b
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/digraph_utils.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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/core/dictionary/digraph_utils.h"
+
+#include <cstdlib>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "utils/char_utils.h"
+
+namespace latinime {
+
+const DigraphUtils::digraph_t DigraphUtils::GERMAN_UMLAUT_DIGRAPHS[] =
+        { { 'a', 'e', 0x00E4 }, // U+00E4 : LATIN SMALL LETTER A WITH DIAERESIS
+        { 'o', 'e', 0x00F6 },   // U+00F6 : LATIN SMALL LETTER O WITH DIAERESIS
+        { 'u', 'e', 0x00FC } }; // U+00FC : LATIN SMALL LETTER U WITH DIAERESIS
+const DigraphUtils::digraph_t DigraphUtils::FRENCH_LIGATURES_DIGRAPHS[] =
+        { { 'a', 'e', 0x00E6 }, // U+00E6 : LATIN SMALL LETTER AE
+        { 'o', 'e', 0x0153 } }; // U+0153 : LATIN SMALL LIGATURE OE
+const DigraphUtils::DigraphType DigraphUtils::USED_DIGRAPH_TYPES[] =
+        { DIGRAPH_TYPE_GERMAN_UMLAUT, DIGRAPH_TYPE_FRENCH_LIGATURES };
+
+/* static */ bool DigraphUtils::hasDigraphForCodePoint(
+        const DictionaryHeaderStructurePolicy *const headerPolicy,
+        const int compositeGlyphCodePoint) {
+    const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(headerPolicy);
+    if (DigraphUtils::getDigraphForDigraphTypeAndCodePoint(digraphType, compositeGlyphCodePoint)) {
+        return true;
+    }
+    return false;
+}
+
+// Returns the digraph type associated with the given dictionary.
+/* static */ DigraphUtils::DigraphType DigraphUtils::getDigraphTypeForDictionary(
+        const DictionaryHeaderStructurePolicy *const headerPolicy) {
+    if (headerPolicy->requiresGermanUmlautProcessing()) {
+        return DIGRAPH_TYPE_GERMAN_UMLAUT;
+    }
+    if (headerPolicy->requiresFrenchLigatureProcessing()) {
+        return DIGRAPH_TYPE_FRENCH_LIGATURES;
+    }
+    return DIGRAPH_TYPE_NONE;
+}
+
+// Returns the digraph codepoint for the given composite glyph codepoint and digraph codepoint index
+// (which specifies the first or second codepoint in the digraph).
+/* static */ int DigraphUtils::getDigraphCodePointForIndex(const int compositeGlyphCodePoint,
+        const DigraphCodePointIndex digraphCodePointIndex) {
+    if (digraphCodePointIndex == NOT_A_DIGRAPH_INDEX) {
+        return NOT_A_CODE_POINT;
+    }
+    const DigraphUtils::digraph_t *const digraph =
+            DigraphUtils::getDigraphForCodePoint(compositeGlyphCodePoint);
+    if (!digraph) {
+        return NOT_A_CODE_POINT;
+    }
+    if (digraphCodePointIndex == FIRST_DIGRAPH_CODEPOINT) {
+        return digraph->first;
+    } else if (digraphCodePointIndex == SECOND_DIGRAPH_CODEPOINT) {
+        return digraph->second;
+    }
+    ASSERT(false);
+    return NOT_A_CODE_POINT;
+}
+
+// Retrieves the set of all digraphs associated with the given digraph type.
+// Returns the size of the digraph array, or 0 if none exist.
+/* static */ int DigraphUtils::getAllDigraphsForDigraphTypeAndReturnSize(
+        const DigraphUtils::DigraphType digraphType,
+        const DigraphUtils::digraph_t **const digraphs) {
+    if (digraphType == DigraphUtils::DIGRAPH_TYPE_GERMAN_UMLAUT) {
+        *digraphs = GERMAN_UMLAUT_DIGRAPHS;
+        return NELEMS(GERMAN_UMLAUT_DIGRAPHS);
+    }
+    if (digraphType == DIGRAPH_TYPE_FRENCH_LIGATURES) {
+        *digraphs = FRENCH_LIGATURES_DIGRAPHS;
+        return NELEMS(FRENCH_LIGATURES_DIGRAPHS);
+    }
+    return 0;
+}
+
+/**
+ * Returns the digraph for the input composite glyph codepoint, or 0 if none exists.
+ * compositeGlyphCodePoint: the method returns the digraph corresponding to this codepoint.
+ */
+/* static */ const DigraphUtils::digraph_t *DigraphUtils::getDigraphForCodePoint(
+        const int compositeGlyphCodePoint) {
+    for (size_t i = 0; i < NELEMS(USED_DIGRAPH_TYPES); i++) {
+        const DigraphUtils::digraph_t *const digraph = getDigraphForDigraphTypeAndCodePoint(
+                USED_DIGRAPH_TYPES[i], compositeGlyphCodePoint);
+        if (digraph) {
+            return digraph;
+        }
+    }
+    return 0;
+}
+
+/**
+ * Returns the digraph for the input composite glyph codepoint, or 0 if none exists.
+ * digraphType: the type of digraphs supported.
+ * compositeGlyphCodePoint: the method returns the digraph corresponding to this codepoint.
+ */
+/* static */ const DigraphUtils::digraph_t *DigraphUtils::getDigraphForDigraphTypeAndCodePoint(
+        const DigraphUtils::DigraphType digraphType, const int compositeGlyphCodePoint) {
+    const DigraphUtils::digraph_t *digraphs = 0;
+    const int compositeGlyphLowerCodePoint = CharUtils::toLowerCase(compositeGlyphCodePoint);
+    const int digraphsSize =
+            DigraphUtils::getAllDigraphsForDigraphTypeAndReturnSize(digraphType, &digraphs);
+    for (int i = 0; i < digraphsSize; i++) {
+        if (digraphs[i].compositeGlyph == compositeGlyphLowerCodePoint) {
+            return &digraphs[i];
+        }
+    }
+    return 0;
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/core/dictionary/digraph_utils.h b/src/aosp/suggest/core/dictionary/digraph_utils.h
new file mode 100644
index 0000000..6ae16e3
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/digraph_utils.h
@@ -0,0 +1,62 @@
+/*
+ * 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 DIGRAPH_UTILS_H
+#define DIGRAPH_UTILS_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DictionaryHeaderStructurePolicy;
+
+class DigraphUtils {
+ public:
+    typedef enum {
+        NOT_A_DIGRAPH_INDEX,
+        FIRST_DIGRAPH_CODEPOINT,
+        SECOND_DIGRAPH_CODEPOINT
+    } DigraphCodePointIndex;
+
+    typedef enum {
+        DIGRAPH_TYPE_NONE,
+        DIGRAPH_TYPE_GERMAN_UMLAUT,
+        DIGRAPH_TYPE_FRENCH_LIGATURES
+    } DigraphType;
+
+    typedef struct { int first; int second; int compositeGlyph; } digraph_t;
+
+    static bool hasDigraphForCodePoint(const DictionaryHeaderStructurePolicy *const headerPolicy,
+            const int compositeGlyphCodePoint);
+    static int getDigraphCodePointForIndex(const int compositeGlyphCodePoint,
+            const DigraphCodePointIndex digraphCodePointIndex);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DigraphUtils);
+    static DigraphType getDigraphTypeForDictionary(
+            const DictionaryHeaderStructurePolicy *const headerPolicy);
+    static int getAllDigraphsForDigraphTypeAndReturnSize(
+            const DigraphType digraphType, const digraph_t **const digraphs);
+    static const digraph_t *getDigraphForCodePoint(const int compositeGlyphCodePoint);
+    static const digraph_t *getDigraphForDigraphTypeAndCodePoint(
+            const DigraphType digraphType, const int compositeGlyphCodePoint);
+
+    static const digraph_t GERMAN_UMLAUT_DIGRAPHS[];
+    static const digraph_t FRENCH_LIGATURES_DIGRAPHS[];
+    static const DigraphType USED_DIGRAPH_TYPES[];
+};
+} // namespace latinime
+#endif // DIGRAPH_UTILS_H
diff --git a/src/aosp/suggest/core/dictionary/multi_bigram_map.cpp b/src/aosp/suggest/core/dictionary/multi_bigram_map.cpp
new file mode 100644
index 0000000..b1d2f4b
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/multi_bigram_map.cpp
@@ -0,0 +1,33 @@
+/*
+ * 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/core/dictionary/multi_bigram_map.h"
+
+#include <cstddef>
+
+namespace latinime {
+
+// Max number of bigram maps (previous word contexts) to be cached. Increasing this number
+// could improve bigram lookup speed for multi-word suggestions, but at the cost of more memory
+// usage. Also, there are diminishing returns since the most frequently used bigrams are
+// typically near the beginning of the input and are thus the first ones to be cached. Note
+// that these bigrams are reset for each new composing word.
+const size_t MultiBigramMap::MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP = 25;
+
+// Most common previous word contexts currently have 100 bigrams
+const int MultiBigramMap::BigramMap::DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP = 100;
+
+} // namespace latinime
diff --git a/src/aosp/suggest/core/dictionary/multi_bigram_map.h b/src/aosp/suggest/core/dictionary/multi_bigram_map.h
new file mode 100644
index 0000000..4633c07
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/multi_bigram_map.h
@@ -0,0 +1,132 @@
+/*
+ * 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_MULTI_BIGRAM_MAP_H
+#define LATINIME_MULTI_BIGRAM_MAP_H
+
+#include <cstddef>
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
+#include "suggest/core/dictionary/bloom_filter.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+// Class for caching bigram maps for multiple previous word contexts. This is useful since the
+// algorithm needs to look up the set of bigrams for every word pair that occurs in every
+// multi-word suggestion.
+class MultiBigramMap {
+ public:
+    MultiBigramMap() : mBigramMaps() {}
+    ~MultiBigramMap() {}
+
+    // Look up the bigram probability for the given word pair from the cached bigram maps.
+    // Also caches the bigrams if there is space remaining and they have not been cached already.
+    int getBigramProbability(const DictionaryStructureWithBufferPolicy *const structurePolicy,
+            const int wordPosition, const int nextWordPosition, const int unigramProbability) {
+        hash_map_compat<int, BigramMap>::const_iterator mapPosition =
+                mBigramMaps.find(wordPosition);
+        if (mapPosition != mBigramMaps.end()) {
+            return mapPosition->second.getBigramProbability(structurePolicy, nextWordPosition,
+                    unigramProbability);
+        }
+        if (mBigramMaps.size() < MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP) {
+            addBigramsForWordPosition(structurePolicy, wordPosition);
+            return mBigramMaps[wordPosition].getBigramProbability(structurePolicy,
+                    nextWordPosition, unigramProbability);
+        }
+        return readBigramProbabilityFromBinaryDictionary(structurePolicy, wordPosition,
+                nextWordPosition, unigramProbability);
+    }
+
+    void clear() {
+        mBigramMaps.clear();
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(MultiBigramMap);
+
+    class BigramMap {
+     public:
+        BigramMap() : mBigramMap(DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP), mBloomFilter() {}
+        ~BigramMap() {}
+
+        void init(const DictionaryStructureWithBufferPolicy *const structurePolicy,
+                const int nodePos) {
+            const int bigramsListPos = structurePolicy->getBigramsPositionOfPtNode(nodePos);
+            BinaryDictionaryBigramsIterator bigramsIt(structurePolicy->getBigramsStructurePolicy(),
+                    bigramsListPos);
+            while (bigramsIt.hasNext()) {
+                bigramsIt.next();
+                if (bigramsIt.getBigramPos() == NOT_A_DICT_POS) {
+                    continue;
+                }
+                mBigramMap[bigramsIt.getBigramPos()] = bigramsIt.getProbability();
+                mBloomFilter.setInFilter(bigramsIt.getBigramPos());
+            }
+        }
+
+        AK_FORCE_INLINE int getBigramProbability(
+                const DictionaryStructureWithBufferPolicy *const structurePolicy,
+                const int nextWordPosition, const int unigramProbability) const {
+            int bigramProbability = NOT_A_PROBABILITY;
+            if (mBloomFilter.isInFilter(nextWordPosition)) {
+                const hash_map_compat<int, int>::const_iterator bigramProbabilityIt =
+                        mBigramMap.find(nextWordPosition);
+                if (bigramProbabilityIt != mBigramMap.end()) {
+                    bigramProbability = bigramProbabilityIt->second;
+                }
+            }
+            return structurePolicy->getProbability(unigramProbability, bigramProbability);
+        }
+
+     private:
+        // NOTE: The BigramMap class doesn't use DISALLOW_COPY_AND_ASSIGN() because its default
+        // copy constructor is needed for use in hash_map.
+        static const int DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP;
+        hash_map_compat<int, int> mBigramMap;
+        BloomFilter mBloomFilter;
+    };
+
+    AK_FORCE_INLINE void addBigramsForWordPosition(
+            const DictionaryStructureWithBufferPolicy *const structurePolicy, const int position) {
+        mBigramMaps[position].init(structurePolicy, position);
+    }
+
+    AK_FORCE_INLINE int readBigramProbabilityFromBinaryDictionary(
+            const DictionaryStructureWithBufferPolicy *const structurePolicy, const int nodePos,
+            const int nextWordPosition, const int unigramProbability) {
+        int bigramProbability = NOT_A_PROBABILITY;
+        const int bigramsListPos = structurePolicy->getBigramsPositionOfPtNode(nodePos);
+        BinaryDictionaryBigramsIterator bigramsIt(structurePolicy->getBigramsStructurePolicy(),
+                bigramsListPos);
+        while (bigramsIt.hasNext()) {
+            bigramsIt.next();
+            if (bigramsIt.getBigramPos() == nextWordPosition) {
+                bigramProbability = bigramsIt.getProbability();
+                break;
+            }
+        }
+        return structurePolicy->getProbability(unigramProbability, bigramProbability);
+    }
+
+    static const size_t MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP;
+    hash_map_compat<int, BigramMap> mBigramMaps;
+};
+} // namespace latinime
+#endif // LATINIME_MULTI_BIGRAM_MAP_H
diff --git a/src/aosp/suggest/core/dictionary/shortcut_utils.h b/src/aosp/suggest/core/dictionary/shortcut_utils.h
new file mode 100644
index 0000000..9ccef02
--- /dev/null
+++ b/src/aosp/suggest/core/dictionary/shortcut_utils.h
@@ -0,0 +1,64 @@
+/*
+ * 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_SHORTCUT_UTILS
+#define LATINIME_SHORTCUT_UTILS
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dictionary/binary_dictionary_shortcut_iterator.h"
+
+namespace latinime {
+
+class ShortcutUtils {
+ public:
+    static int outputShortcuts(BinaryDictionaryShortcutIterator *const shortcutIt,
+            int outputWordIndex, const int finalScore, int *const outputCodePoints,
+            int *const frequencies, int *const outputTypes, const bool sameAsTyped) {
+        int shortcutTarget[MAX_WORD_LENGTH];
+        while (shortcutIt->hasNextShortcutTarget() && outputWordIndex < MAX_RESULTS) {
+            bool isWhilelist;
+            int shortcutTargetStringLength;
+            shortcutIt->nextShortcutTarget(MAX_WORD_LENGTH, shortcutTarget,
+                    &shortcutTargetStringLength, &isWhilelist);
+            int shortcutScore;
+            int kind;
+            if (isWhilelist && sameAsTyped) {
+                shortcutScore = S_INT_MAX;
+                kind = Dictionary::KIND_WHITELIST;
+            } else {
+                // shortcut entry's score == its base entry's score - 1
+                shortcutScore = finalScore;
+                // Protection against int underflow
+                shortcutScore = max(S_INT_MIN + 1, shortcutScore) - 1;
+                kind = Dictionary::KIND_SHORTCUT;
+            }
+            outputTypes[outputWordIndex] = kind;
+            frequencies[outputWordIndex] = shortcutScore;
+            frequencies[outputWordIndex] = max(S_INT_MIN + 1, shortcutScore) - 1;
+            const int startIndex2 = outputWordIndex * MAX_WORD_LENGTH;
+            DicNodeUtils::appendTwoWords(0, 0, shortcutTarget, shortcutTargetStringLength,
+                    &outputCodePoints[startIndex2]);
+            ++outputWordIndex;
+        }
+        return outputWordIndex;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ShortcutUtils);
+};
+} // namespace latinime
+#endif // LATINIME_SHORTCUT_UTILS
diff --git a/src/aosp/suggest/core/layout/additional_proximity_chars.cpp b/src/aosp/suggest/core/layout/additional_proximity_chars.cpp
new file mode 100644
index 0000000..34b8b37
--- /dev/null
+++ b/src/aosp/suggest/core/layout/additional_proximity_chars.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/layout/additional_proximity_chars.h"
+
+namespace latinime {
+// TODO: Stop using hardcoded additional proximity characters.
+// TODO: Have proximity character informations in each language's binary dictionary.
+const char *AdditionalProximityChars::LOCALE_EN_US = "en";
+
+const int AdditionalProximityChars::EN_US_ADDITIONAL_A[EN_US_ADDITIONAL_A_SIZE] = {
+    'e', 'i', 'o', 'u'
+};
+
+const int AdditionalProximityChars::EN_US_ADDITIONAL_E[EN_US_ADDITIONAL_E_SIZE] = {
+    'a', 'i', 'o', 'u'
+};
+
+const int AdditionalProximityChars::EN_US_ADDITIONAL_I[EN_US_ADDITIONAL_I_SIZE] = {
+    'a', 'e', 'o', 'u'
+};
+
+const int AdditionalProximityChars::EN_US_ADDITIONAL_O[EN_US_ADDITIONAL_O_SIZE] = {
+    'a', 'e', 'i', 'u'
+};
+
+const int AdditionalProximityChars::EN_US_ADDITIONAL_U[EN_US_ADDITIONAL_U_SIZE] = {
+    'a', 'e', 'i', 'o'
+};
+} // namespace latinime
diff --git a/src/aosp/suggest/core/layout/additional_proximity_chars.h b/src/aosp/suggest/core/layout/additional_proximity_chars.h
new file mode 100644
index 0000000..a88fd6c
--- /dev/null
+++ b/src/aosp/suggest/core/layout/additional_proximity_chars.h
@@ -0,0 +1,89 @@
+/*
+ * 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_ADDITIONAL_PROXIMITY_CHARS_H
+#define LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
+
+#include <cstring>
+
+#include "defines.h"
+
+namespace latinime {
+
+class AdditionalProximityChars {
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(AdditionalProximityChars);
+    static const char *LOCALE_EN_US;
+    static const int EN_US_ADDITIONAL_A_SIZE = 4;
+    static const int EN_US_ADDITIONAL_A[];
+    static const int EN_US_ADDITIONAL_E_SIZE = 4;
+    static const int EN_US_ADDITIONAL_E[];
+    static const int EN_US_ADDITIONAL_I_SIZE = 4;
+    static const int EN_US_ADDITIONAL_I[];
+    static const int EN_US_ADDITIONAL_O_SIZE = 4;
+    static const int EN_US_ADDITIONAL_O[];
+    static const int EN_US_ADDITIONAL_U_SIZE = 4;
+    static const int EN_US_ADDITIONAL_U[];
+
+    AK_FORCE_INLINE static bool isEnLocale(const char *localeStr) {
+        const size_t LOCALE_EN_US_SIZE = strlen(LOCALE_EN_US);
+        return localeStr && strlen(localeStr) >= LOCALE_EN_US_SIZE
+                && strncmp(localeStr, LOCALE_EN_US, LOCALE_EN_US_SIZE) == 0;
+    }
+
+ public:
+    static int getAdditionalCharsSize(const char *const localeStr, const int c) {
+        if (!isEnLocale(localeStr)) {
+            return 0;
+        }
+        switch (c) {
+        case 'a':
+            return EN_US_ADDITIONAL_A_SIZE;
+        case 'e':
+            return EN_US_ADDITIONAL_E_SIZE;
+        case 'i':
+            return EN_US_ADDITIONAL_I_SIZE;
+        case 'o':
+            return EN_US_ADDITIONAL_O_SIZE;
+        case 'u':
+            return EN_US_ADDITIONAL_U_SIZE;
+        default:
+            return 0;
+        }
+    }
+
+    static const int *getAdditionalChars(const char *const localeStr, const int c) {
+        if (!isEnLocale(localeStr)) {
+            return 0;
+        }
+        switch (c) {
+        case 'a':
+            return EN_US_ADDITIONAL_A;
+        case 'e':
+            return EN_US_ADDITIONAL_E;
+        case 'i':
+            return EN_US_ADDITIONAL_I;
+        case 'o':
+            return EN_US_ADDITIONAL_O;
+        case 'u':
+            return EN_US_ADDITIONAL_U;
+        default:
+            return 0;
+        }
+    }
+};
+} // namespace latinime
+#endif // LATINIME_ADDITIONAL_PROXIMITY_CHARS_H
diff --git a/src/aosp/suggest/core/layout/geometry_utils.h b/src/aosp/suggest/core/layout/geometry_utils.h
new file mode 100644
index 0000000..b667df6
--- /dev/null
+++ b/src/aosp/suggest/core/layout/geometry_utils.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_GEOMETRY_UTILS_H
+#define LATINIME_GEOMETRY_UTILS_H
+
+#include <cmath>
+
+#include "defines.h"
+
+#define ROUND_FLOAT_10000(f) ((f) < 1000.0f && (f) > 0.001f) \
+        ? (floorf((f) * 10000.0f) / 10000.0f) : (f)
+
+namespace latinime {
+
+class GeometryUtils {
+ public:
+    static inline float SQUARE_FLOAT(const float x) { return x * x; }
+
+    static AK_FORCE_INLINE float getAngle(const int x1, const int y1, const int x2, const int y2) {
+        const int dx = x1 - x2;
+        const int dy = y1 - y2;
+        if (dx == 0 && dy == 0) return 0.0f;
+        return atan2f(static_cast<float>(dy), static_cast<float>(dx));
+    }
+
+    static AK_FORCE_INLINE float getAngleDiff(const float a1, const float a2) {
+        const float deltaA = fabsf(a1 - a2);
+        const float diff = ROUND_FLOAT_10000(deltaA);
+        if (diff > M_PI_F) {
+            const float normalizedDiff = 2.0f * M_PI_F - diff;
+            return ROUND_FLOAT_10000(normalizedDiff);
+        }
+        return diff;
+    }
+
+    static AK_FORCE_INLINE int getDistanceInt(const int x1, const int y1, const int x2,
+            const int y2) {
+        return static_cast<int>(hypotf(static_cast<float>(x1 - x2), static_cast<float>(y1 - y2)));
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(GeometryUtils);
+};
+} // namespace latinime
+#endif // LATINIME_GEOMETRY_UTILS_H
diff --git a/src/aosp/suggest/core/layout/proximity_info.cpp b/src/aosp/suggest/core/layout/proximity_info.cpp
new file mode 100644
index 0000000..897de1c
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "LatinIME: proximity_info.cpp"
+
+#include "suggest/core/layout/proximity_info.h"
+
+#include <cstring>
+#include <cmath>
+
+#include "defines.h"
+#include "jni.h"
+#include "suggest/core/layout/additional_proximity_chars.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info_params.h"
+#include "utils/char_utils.h"
+
+namespace latinime {
+
+static AK_FORCE_INLINE void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray,
+        jsize len, jint *buffer) {
+    if (jArray && buffer) {
+        env->GetIntArrayRegion(jArray, 0, len, buffer);
+    } else if (buffer) {
+        memset(buffer, 0, len * sizeof(buffer[0]));
+    }
+}
+
+static AK_FORCE_INLINE void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray,
+        jsize len, jfloat *buffer) {
+    if (jArray && buffer) {
+        env->GetFloatArrayRegion(jArray, 0, len, buffer);
+    } else if (buffer) {
+        memset(buffer, 0, len * sizeof(buffer[0]));
+    }
+}
+
+ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr,
+        const int keyboardWidth, const int keyboardHeight, const int gridWidth,
+        const int gridHeight, const int mostCommonKeyWidth, const int mostCommonKeyHeight,
+        const jintArray proximityChars, const int keyCount, const jintArray keyXCoordinates,
+        const jintArray keyYCoordinates, const jintArray keyWidths, const jintArray keyHeights,
+        const jintArray keyCharCodes, const jfloatArray sweetSpotCenterXs,
+        const jfloatArray sweetSpotCenterYs, const jfloatArray sweetSpotRadii)
+        : GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight), MOST_COMMON_KEY_WIDTH(mostCommonKeyWidth),
+          MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
+          MOST_COMMON_KEY_HEIGHT(mostCommonKeyHeight),
+          NORMALIZED_SQUARED_MOST_COMMON_KEY_HYPOTENUSE(1.0f +
+                  GeometryUtils::SQUARE_FLOAT(static_cast<float>(mostCommonKeyHeight) /
+                          static_cast<float>(mostCommonKeyWidth))),
+          CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
+          CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
+          KEY_COUNT(min(keyCount, MAX_KEY_COUNT_IN_A_KEYBOARD)),
+          KEYBOARD_WIDTH(keyboardWidth), KEYBOARD_HEIGHT(keyboardHeight),
+          KEYBOARD_HYPOTENUSE(hypotf(KEYBOARD_WIDTH, KEYBOARD_HEIGHT)),
+          HAS_TOUCH_POSITION_CORRECTION_DATA(keyCount > 0 && keyXCoordinates && keyYCoordinates
+                  && keyWidths && keyHeights && keyCharCodes && sweetSpotCenterXs
+                  && sweetSpotCenterYs && sweetSpotRadii),
+          mProximityCharsArray(new int[GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE
+                  /* proximityCharsLength */]),
+          mCodeToKeyMap() {
+    /* Let's check the input array length here to make sure */
+    const jsize proximityCharsLength = env->GetArrayLength(proximityChars);
+    if (proximityCharsLength != GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE) {
+        AKLOGE("Invalid proximityCharsLength: %d", proximityCharsLength);
+        ASSERT(false);
+        return;
+    }
+
+
+    if (DEBUG_PROXIMITY_INFO) {
+        AKLOGI("Create proximity info array %d", proximityCharsLength);
+    }
+    const jsize localeCStrUtf8Length = env->GetStringUTFLength(localeJStr);
+    if (localeCStrUtf8Length >= MAX_LOCALE_STRING_LENGTH) {
+        AKLOGI("Locale string length too long: length=%d", localeCStrUtf8Length);
+        ASSERT(false);
+    }
+    memset(mLocaleStr, 0, sizeof(mLocaleStr));
+    env->GetStringUTFRegion(localeJStr, 0, env->GetStringLength(localeJStr), mLocaleStr);
+    safeGetOrFillZeroIntArrayRegion(env, proximityChars, proximityCharsLength,
+            mProximityCharsArray);
+
+    for (int x=0; x<GRID_WIDTH; ++x) {
+        for (int y=0; y<GRID_HEIGHT; ++y) {
+            AKLOGI("(%d, %d) = %c %c %c %c %c %c %c %c", x, y,
+                   (char)mProximityCharsArray[((y * GRID_WIDTH) + x) * MAX_PROXIMITY_CHARS_SIZE + 0],
+                   (char)mProximityCharsArray[((y * GRID_WIDTH) + x) * MAX_PROXIMITY_CHARS_SIZE + 1],
+                   (char)mProximityCharsArray[((y * GRID_WIDTH) + x) * MAX_PROXIMITY_CHARS_SIZE + 2],
+                   (char)mProximityCharsArray[((y * GRID_WIDTH) + x) * MAX_PROXIMITY_CHARS_SIZE + 3],
+                   (char)mProximityCharsArray[((y * GRID_WIDTH) + x) * MAX_PROXIMITY_CHARS_SIZE + 4],
+                   (char)mProximityCharsArray[((y * GRID_WIDTH) + x) * MAX_PROXIMITY_CHARS_SIZE + 5],
+                   (char)mProximityCharsArray[((y * GRID_WIDTH) + x) * MAX_PROXIMITY_CHARS_SIZE + 6],
+                   (char)mProximityCharsArray[((y * GRID_WIDTH) + x) * MAX_PROXIMITY_CHARS_SIZE + 7]);
+        }
+    }
+    safeGetOrFillZeroIntArrayRegion(env, keyXCoordinates, KEY_COUNT, mKeyXCoordinates);
+    safeGetOrFillZeroIntArrayRegion(env, keyYCoordinates, KEY_COUNT, mKeyYCoordinates);
+    safeGetOrFillZeroIntArrayRegion(env, keyWidths, KEY_COUNT, mKeyWidths);
+    safeGetOrFillZeroIntArrayRegion(env, keyHeights, KEY_COUNT, mKeyHeights);
+    safeGetOrFillZeroIntArrayRegion(env, keyCharCodes, KEY_COUNT, mKeyCodePoints);
+    safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterXs, KEY_COUNT, mSweetSpotCenterXs);
+    safeGetOrFillZeroFloatArrayRegion(env, sweetSpotCenterYs, KEY_COUNT, mSweetSpotCenterYs);
+    safeGetOrFillZeroFloatArrayRegion(env, sweetSpotRadii, KEY_COUNT, mSweetSpotRadii);
+    initializeG();
+}
+
+ProximityInfo::~ProximityInfo() {
+    delete[] mProximityCharsArray;
+}
+
+bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
+    if (x < 0 || y < 0) {
+        if (DEBUG_DICT) {
+            AKLOGI("HasSpaceProximity: Illegal coordinates (%d, %d)", x, y);
+            // TODO: Enable this assertion.
+            //ASSERT(false);
+        }
+        return false;
+    }
+
+    const int startIndex = ProximityInfoUtils::getStartIndexFromCoordinates(x, y,
+            CELL_HEIGHT, CELL_WIDTH, GRID_WIDTH);
+    if (DEBUG_PROXIMITY_INFO) {
+        AKLOGI("hasSpaceProximity: index %d, %d, %d", startIndex, x, y);
+    }
+    int *proximityCharsArray = mProximityCharsArray;
+    for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+        if (DEBUG_PROXIMITY_INFO) {
+            AKLOGI("Index: %d", mProximityCharsArray[startIndex + i]);
+        }
+        if (proximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
+            return true;
+        }
+    }
+    return false;
+}
+
+float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloatG(
+        const int keyId, const int x, const int y, const bool isGeometric) const {
+    const float centerX = static_cast<float>(getKeyCenterXOfKeyIdG(keyId, x, isGeometric));
+    const float centerY = static_cast<float>(getKeyCenterYOfKeyIdG(keyId, y, isGeometric));
+    const float touchX = static_cast<float>(x);
+    const float touchY = static_cast<float>(y);
+    return ProximityInfoUtils::getSquaredDistanceFloat(centerX, centerY, touchX, touchY)
+            / GeometryUtils::SQUARE_FLOAT(static_cast<float>(getMostCommonKeyWidth()));
+}
+
+int ProximityInfo::getCodePointOf(const int keyIndex) const {
+    if (keyIndex < 0 || keyIndex >= KEY_COUNT) {
+        return NOT_A_CODE_POINT;
+    }
+    return mKeyIndexToCodePointG[keyIndex];
+}
+
+void ProximityInfo::initializeG() {
+    // TODO: Optimize
+    for (int i = 0; i < KEY_COUNT; ++i) {
+        const int code = mKeyCodePoints[i];
+        const int lowerCode = CharUtils::toLowerCase(code);
+        mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
+        mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
+        if (hasTouchPositionCorrectionData()) {
+            // Computes sweet spot center points for geometric input.
+            const float verticalScale = ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G;
+            const float sweetSpotCenterY = static_cast<float>(mSweetSpotCenterYs[i]);
+            const float gapY = sweetSpotCenterY - mCenterYsG[i];
+            mSweetSpotCenterYsG[i] = static_cast<int>(mCenterYsG[i] + gapY * verticalScale);
+        }
+        mCodeToKeyMap[lowerCode] = i;
+        mKeyIndexToCodePointG[i] = lowerCode;
+    }
+    for (int i = 0; i < KEY_COUNT; i++) {
+        mKeyKeyDistancesG[i][i] = 0;
+        for (int j = i + 1; j < KEY_COUNT; j++) {
+            if (hasTouchPositionCorrectionData()) {
+                // Computes distances using sweet spots if they exist.
+                // We have two types of Y coordinate sweet spots, for geometric and for the others.
+                // The sweet spots for geometric input are used for calculating key-key distances
+                // here.
+                mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
+                        mSweetSpotCenterXs[i], mSweetSpotCenterYsG[i],
+                        mSweetSpotCenterXs[j], mSweetSpotCenterYsG[j]);
+            } else {
+                mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
+                        mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
+            }
+            mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
+        }
+    }
+}
+
+// referencePointX is used only for keys wider than most common key width. When the referencePointX
+// is NOT_A_COORDINATE, this method calculates the return value without using the line segment.
+// isGeometric is currently not used because we don't have extra X coordinates sweet spots for
+// geometric input.
+int ProximityInfo::getKeyCenterXOfKeyIdG(
+        const int keyId, const int referencePointX, const bool isGeometric) const {
+    if (keyId < 0) {
+        return 0;
+    }
+    int centerX = (hasTouchPositionCorrectionData()) ? static_cast<int>(mSweetSpotCenterXs[keyId])
+            : mCenterXsG[keyId];
+    const int keyWidth = mKeyWidths[keyId];
+    if (referencePointX != NOT_A_COORDINATE
+            && keyWidth > getMostCommonKeyWidth()) {
+        // For keys wider than most common keys, we use a line segment instead of the center point;
+        // thus, centerX is adjusted depending on referencePointX.
+        const int keyWidthHalfDiff = (keyWidth - getMostCommonKeyWidth()) / 2;
+        if (referencePointX < centerX - keyWidthHalfDiff) {
+            centerX -= keyWidthHalfDiff;
+        } else if (referencePointX > centerX + keyWidthHalfDiff) {
+            centerX += keyWidthHalfDiff;
+        } else {
+            centerX = referencePointX;
+        }
+    }
+    return centerX;
+}
+
+// When the referencePointY is NOT_A_COORDINATE, this method calculates the return value without
+// using the line segment.
+int ProximityInfo::getKeyCenterYOfKeyIdG(
+        const int keyId,  const int referencePointY, const bool isGeometric) const {
+    // TODO: Remove "isGeometric" and have separate "proximity_info"s for gesture and typing.
+    if (keyId < 0) {
+        return 0;
+    }
+    int centerY;
+    if (!hasTouchPositionCorrectionData()) {
+        centerY = mCenterYsG[keyId];
+    } else if (isGeometric) {
+        centerY = static_cast<int>(mSweetSpotCenterYsG[keyId]);
+    } else {
+        centerY = static_cast<int>(mSweetSpotCenterYs[keyId]);
+    }
+    if (referencePointY != NOT_A_COORDINATE &&
+            centerY + mKeyHeights[keyId] > KEYBOARD_HEIGHT && centerY < referencePointY) {
+        // When the distance between center point and bottom edge of the keyboard is shorter than
+        // the key height, we assume the key is located at the bottom row of the keyboard.
+        // The center point is extended to the bottom edge for such keys.
+        return referencePointY;
+    }
+    return centerY;
+}
+
+int ProximityInfo::getKeyKeyDistanceG(const int keyId0, const int keyId1) const {
+    if (keyId0 >= 0 && keyId1 >= 0) {
+        return mKeyKeyDistancesG[keyId0][keyId1];
+    }
+    return MAX_VALUE_FOR_WEIGHTING;
+}
+} // namespace latinime
diff --git a/src/aosp/suggest/core/layout/proximity_info.h b/src/aosp/suggest/core/layout/proximity_info.h
new file mode 100644
index 0000000..f259490
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_PROXIMITY_INFO_H
+#define LATINIME_PROXIMITY_INFO_H
+
+#include "defines.h"
+#include "jni.h"
+#include "suggest/core/layout/proximity_info_utils.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+class ProximityInfo {
+ public:
+    ProximityInfo(JNIEnv *env, const jstring localeJStr,
+            const int keyboardWidth, const int keyboardHeight, const int gridWidth,
+            const int gridHeight, const int mostCommonKeyWidth, const int mostCommonKeyHeight,
+            const jintArray proximityChars, const int keyCount, const jintArray keyXCoordinates,
+            const jintArray keyYCoordinates, const jintArray keyWidths, const jintArray keyHeights,
+            const jintArray keyCharCodes, const jfloatArray sweetSpotCenterXs,
+            const jfloatArray sweetSpotCenterYs, const jfloatArray sweetSpotRadii);
+    ~ProximityInfo();
+    bool hasSpaceProximity(const int x, const int y) const;
+    int getNormalizedSquaredDistance(const int inputIndex, const int proximityIndex) const;
+    float getNormalizedSquaredDistanceFromCenterFloatG(
+            const int keyId, const int x, const int y, const bool isGeometric) const;
+    int getCodePointOf(const int keyIndex) const;
+    bool hasSweetSpotData(const int keyIndex) const {
+        // When there are no calibration data for a key,
+        // the radius of the key is assigned to zero.
+        return mSweetSpotRadii[keyIndex] > 0.0f;
+    }
+    float getSweetSpotRadiiAt(int keyIndex) const { return mSweetSpotRadii[keyIndex]; }
+    float getSweetSpotCenterXAt(int keyIndex) const { return mSweetSpotCenterXs[keyIndex]; }
+    float getSweetSpotCenterYAt(int keyIndex) const { return mSweetSpotCenterYs[keyIndex]; }
+    void calculateNearbyKeyCodes(
+            const int x, const int y, const int primaryKey, int *inputCodes) const;
+    bool hasTouchPositionCorrectionData() const { return HAS_TOUCH_POSITION_CORRECTION_DATA; }
+    int getMostCommonKeyWidth() const { return MOST_COMMON_KEY_WIDTH; }
+    int getMostCommonKeyWidthSquare() const { return MOST_COMMON_KEY_WIDTH_SQUARE; }
+    float getNormalizedSquaredMostCommonKeyHypotenuse() const {
+        return NORMALIZED_SQUARED_MOST_COMMON_KEY_HYPOTENUSE;
+    }
+    int getKeyCount() const { return KEY_COUNT; }
+    int getCellHeight() const { return CELL_HEIGHT; }
+    int getCellWidth() const { return CELL_WIDTH; }
+    int getGridWidth() const { return GRID_WIDTH; }
+    int getGridHeight() const { return GRID_HEIGHT; }
+    int getKeyboardWidth() const { return KEYBOARD_WIDTH; }
+    int getKeyboardHeight() const { return KEYBOARD_HEIGHT; }
+    float getKeyboardHypotenuse() const { return KEYBOARD_HYPOTENUSE; }
+
+    int getKeyCenterXOfKeyIdG(
+            const int keyId, const int referencePointX, const bool isGeometric) const;
+    int getKeyCenterYOfKeyIdG(
+            const int keyId, const int referencePointY, const bool isGeometric) const;
+    int getKeyKeyDistanceG(int keyId0, int keyId1) const;
+
+    AK_FORCE_INLINE void initializeProximities(const int *const inputCodes,
+            const int *const inputXCoordinates, const int *const inputYCoordinates,
+            const int inputSize, int *allInputCodes) const {
+        ProximityInfoUtils::initializeProximities(inputCodes, inputXCoordinates, inputYCoordinates,
+                inputSize, mKeyXCoordinates, mKeyYCoordinates, mKeyWidths, mKeyHeights,
+                mProximityCharsArray, CELL_HEIGHT, CELL_WIDTH, GRID_WIDTH, MOST_COMMON_KEY_WIDTH,
+                KEY_COUNT, mLocaleStr, &mCodeToKeyMap, allInputCodes);
+    }
+
+    AK_FORCE_INLINE int getKeyIndexOf(const int c) const {
+        return ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, c, &mCodeToKeyMap);
+    }
+
+    AK_FORCE_INLINE bool isCodePointOnKeyboard(const int codePoint) const {
+        return getKeyIndexOf(codePoint) != NOT_AN_INDEX;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo);
+
+    void initializeG();
+
+    const int GRID_WIDTH;
+    const int GRID_HEIGHT;
+    const int MOST_COMMON_KEY_WIDTH;
+    const int MOST_COMMON_KEY_WIDTH_SQUARE;
+    const int MOST_COMMON_KEY_HEIGHT;
+    const float NORMALIZED_SQUARED_MOST_COMMON_KEY_HYPOTENUSE;
+    const int CELL_WIDTH;
+    const int CELL_HEIGHT;
+    const int KEY_COUNT;
+    const int KEYBOARD_WIDTH;
+    const int KEYBOARD_HEIGHT;
+    const float KEYBOARD_HYPOTENUSE;
+    const bool HAS_TOUCH_POSITION_CORRECTION_DATA;
+    char mLocaleStr[MAX_LOCALE_STRING_LENGTH];
+    int *mProximityCharsArray;
+    int mKeyXCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mKeyYCoordinates[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mKeyWidths[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mKeyHeights[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mKeyCodePoints[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    float mSweetSpotCenterXs[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    // Sweet spots for geometric input. Note that we have extra sweet spots only for Y coordinates.
+    float mSweetSpotCenterYsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    hash_map_compat<int, int> mCodeToKeyMap;
+
+    int mKeyIndexToCodePointG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mCenterXsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mCenterYsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    int mKeyKeyDistancesG[MAX_KEY_COUNT_IN_A_KEYBOARD][MAX_KEY_COUNT_IN_A_KEYBOARD];
+    // TODO: move to correction.h
+};
+} // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_H
diff --git a/src/aosp/suggest/core/layout/proximity_info_params.cpp b/src/aosp/suggest/core/layout/proximity_info_params.cpp
new file mode 100644
index 0000000..49df103
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info_params.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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 "defines.h"
+#include "suggest/core/layout/proximity_info_params.h"
+
+namespace latinime {
+const float ProximityInfoParams::NOT_A_DISTANCE_FLOAT = -1.0f;
+const int ProximityInfoParams::MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE = 5;
+const float ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE = 1.0f;
+const float ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G = 0.5f;
+
+/* Per method constants */
+// Used by ProximityInfoStateUtils::initGeometricDistanceInfos()
+const float ProximityInfoParams::NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD = 4.0f;
+
+// Used by ProximityInfoStateUtils::updateNearKeysDistances()
+const float ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_DISTANCE = 2.0f;
+
+// Used by ProximityInfoStateUtils::isPrevLocalMin()
+const float ProximityInfoParams::MARGIN_FOR_PREV_LOCAL_MIN = 0.01f;
+
+// Used by ProximityInfoStateUtils::getPointScore()
+const int ProximityInfoParams::DISTANCE_BASE_SCALE = 100;
+const float ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_POINT_SCORE = 0.6f;
+const int ProximityInfoParams::CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 25;
+const float ProximityInfoParams::NOT_LOCALMIN_DISTANCE_SCORE = -1.0f;
+const float ProximityInfoParams::LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE = 1.0f;
+const float ProximityInfoParams::CORNER_ANGLE_THRESHOLD_FOR_POINT_SCORE = M_PI_F * 2.0f / 3.0f;
+const float ProximityInfoParams::CORNER_SUM_ANGLE_THRESHOLD = M_PI_F / 4.0f;
+const float ProximityInfoParams::CORNER_SCORE = 1.0f;
+
+// Used by ProximityInfoStateUtils::refreshSpeedRates()
+const int ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION = 2;
+
+// Used by ProximityInfoStateUtils::pushTouchPoint()
+const int ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE = 4;
+
+// Used by ProximityInfoStateUtils::updateAlignPointProbabilities()
+const float ProximityInfoParams::MIN_PROBABILITY = 0.000001f;
+const float ProximityInfoParams::MAX_SKIP_PROBABILITY = 0.95f;
+const float ProximityInfoParams::SKIP_FIRST_POINT_PROBABILITY = 0.01f;
+const float ProximityInfoParams::SKIP_LAST_POINT_PROBABILITY = 0.1f;
+const float ProximityInfoParams::MIN_SPEED_RATE_FOR_SKIP_PROBABILITY = 0.15f;
+const float ProximityInfoParams::SPEED_WEIGHT_FOR_SKIP_PROBABILITY = 0.9f;
+const float ProximityInfoParams::SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY = 0.6f;
+const float ProximityInfoParams::NEAREST_DISTANCE_WEIGHT = 0.5f;
+const float ProximityInfoParams::NEAREST_DISTANCE_BIAS = 0.5f;
+const float ProximityInfoParams::NEAREST_DISTANCE_WEIGHT_FOR_LAST = 0.6f;
+const float ProximityInfoParams::NEAREST_DISTANCE_BIAS_FOR_LAST = 0.4f;
+const float ProximityInfoParams::ANGLE_WEIGHT = 0.90f;
+const float ProximityInfoParams::DEEP_CORNER_ANGLE_THRESHOLD = M_PI_F * 60.0f / 180.0f;
+const float ProximityInfoParams::SKIP_DEEP_CORNER_PROBABILITY = 0.1f;
+const float ProximityInfoParams::CORNER_ANGLE_THRESHOLD = M_PI_F * 30.0f / 180.0f;
+const float ProximityInfoParams::STRAIGHT_ANGLE_THRESHOLD = M_PI_F * 15.0f / 180.0f;
+const float ProximityInfoParams::SKIP_CORNER_PROBABILITY = 0.4f;
+const float ProximityInfoParams::SPEED_MARGIN = 0.1f;
+const float ProximityInfoParams::CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION = 0.0f;
+// TODO: The variance is critical for accuracy; thus, adjusting these parameters by machine
+// learning or something would be efficient.
+const float ProximityInfoParams::SPEEDxANGLE_WEIGHT_FOR_STANDARD_DEVIATION = 0.3f;
+const float ProximityInfoParams::MAX_SPEEDxANGLE_RATE_FOR_STANDARD_DEVIATION = 0.25f;
+const float ProximityInfoParams::SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DEVIATION = 0.5f;
+const float ProximityInfoParams::MAX_SPEEDxNEAREST_RATE_FOR_STANDARD_DEVIATION = 0.15f;
+const float ProximityInfoParams::MIN_STANDARD_DEVIATION = 0.37f;
+const float ProximityInfoParams::PREV_DISTANCE_WEIGHT = 0.5f;
+const float ProximityInfoParams::NEXT_DISTANCE_WEIGHT = 0.6f;
+
+// Used by ProximityInfoStateUtils::suppressCharProbabilities()
+const float ProximityInfoParams::SUPPRESSION_LENGTH_WEIGHT = 1.5f;
+const float ProximityInfoParams::MIN_SUPPRESSION_RATE = 0.1f;
+const float ProximityInfoParams::SUPPRESSION_WEIGHT = 0.5f;
+const float ProximityInfoParams::SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN = 0.1f;
+const float ProximityInfoParams::SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN = 0.3f;
+
+// Used by ProximityInfoStateUtils::getMostProbableString()
+const float ProximityInfoParams::DEMOTION_LOG_PROBABILITY = 0.3f;
+
+// Used by ProximityInfoStateUtils::updateSampledSearchKeySets()
+// TODO: Investigate if this is required
+const float ProximityInfoParams::SEARCH_KEY_RADIUS_RATIO = 0.95f;
+
+// Used by ProximityInfoStateUtils::calculateBeelineSpeedRate()
+const int ProximityInfoParams::LOOKUP_RADIUS_PERCENTILE = 50;
+const int ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS = 150;
+const int ProximityInfoParams::STRONG_DOUBLE_LETTER_TIME_MILLIS = 600;
+
+// Used by ProximityInfoStateUtils::calculateNormalizedSquaredDistance()
+const int ProximityInfoParams::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR = 1 << 10;
+
+} // namespace latinime
diff --git a/src/aosp/suggest/core/layout/proximity_info_params.h b/src/aosp/suggest/core/layout/proximity_info_params.h
new file mode 100644
index 0000000..ae1f82c
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info_params.h
@@ -0,0 +1,109 @@
+/*
+ * 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_PROXIMITY_INFO_PARAMS_H
+#define LATINIME_PROXIMITY_INFO_PARAMS_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class ProximityInfoParams {
+ public:
+    static const float NOT_A_DISTANCE_FLOAT;
+    static const int MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE;
+    static const float VERTICAL_SWEET_SPOT_SCALE;
+    static const float VERTICAL_SWEET_SPOT_SCALE_G;
+
+    // Used by ProximityInfoStateUtils::initGeometricDistanceInfos()
+    static const float NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD;
+
+    // Used by ProximityInfoStateUtils::updateNearKeysDistances()
+    static const float NEAR_KEY_THRESHOLD_FOR_DISTANCE;
+
+    // Used by ProximityInfoStateUtils::isPrevLocalMin()
+    static const float MARGIN_FOR_PREV_LOCAL_MIN;
+
+    // Used by ProximityInfoStateUtils::getPointScore()
+    static const int DISTANCE_BASE_SCALE;
+    static const float NEAR_KEY_THRESHOLD_FOR_POINT_SCORE;
+    static const int CORNER_CHECK_DISTANCE_THRESHOLD_SCALE;
+    static const float NOT_LOCALMIN_DISTANCE_SCORE;
+    static const float LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE;
+    static const float CORNER_ANGLE_THRESHOLD_FOR_POINT_SCORE;
+    static const float CORNER_SUM_ANGLE_THRESHOLD;
+    static const float CORNER_SCORE;
+
+    // Used by ProximityInfoStateUtils::refreshSpeedRates()
+    static const int NUM_POINTS_FOR_SPEED_CALCULATION;
+
+    // Used by ProximityInfoStateUtils::pushTouchPoint()
+    static const int LAST_POINT_SKIP_DISTANCE_SCALE;
+
+    // Used by ProximityInfoStateUtils::updateAlignPointProbabilities()
+    static const float MIN_PROBABILITY;
+    static const float MAX_SKIP_PROBABILITY;
+    static const float SKIP_FIRST_POINT_PROBABILITY;
+    static const float SKIP_LAST_POINT_PROBABILITY;
+    static const float MIN_SPEED_RATE_FOR_SKIP_PROBABILITY;
+    static const float SPEED_WEIGHT_FOR_SKIP_PROBABILITY;
+    static const float SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY;
+    static const float NEAREST_DISTANCE_WEIGHT;
+    static const float NEAREST_DISTANCE_BIAS;
+    static const float NEAREST_DISTANCE_WEIGHT_FOR_LAST;
+    static const float NEAREST_DISTANCE_BIAS_FOR_LAST;
+    static const float ANGLE_WEIGHT;
+    static const float DEEP_CORNER_ANGLE_THRESHOLD;
+    static const float SKIP_DEEP_CORNER_PROBABILITY;
+    static const float CORNER_ANGLE_THRESHOLD;
+    static const float STRAIGHT_ANGLE_THRESHOLD;
+    static const float SKIP_CORNER_PROBABILITY;
+    static const float SPEED_MARGIN;
+    static const float CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION;
+    static const float SPEEDxANGLE_WEIGHT_FOR_STANDARD_DEVIATION;
+    static const float MAX_SPEEDxANGLE_RATE_FOR_STANDARD_DEVIATION;
+    static const float SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DEVIATION;
+    static const float MAX_SPEEDxNEAREST_RATE_FOR_STANDARD_DEVIATION;
+    static const float MIN_STANDARD_DEVIATION;
+    static const float PREV_DISTANCE_WEIGHT;
+    static const float NEXT_DISTANCE_WEIGHT;
+
+    // Used by ProximityInfoStateUtils::suppressCharProbabilities()
+    static const float SUPPRESSION_LENGTH_WEIGHT;
+    static const float MIN_SUPPRESSION_RATE;
+    static const float SUPPRESSION_WEIGHT;
+    static const float SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN;
+    static const float SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN;
+
+    // Used by ProximityInfoStateUtils::getMostProbableString()
+    static const float DEMOTION_LOG_PROBABILITY;
+
+    // Used by ProximityInfoStateUtils::updateSampledSearchKeySets()
+    static const float SEARCH_KEY_RADIUS_RATIO;
+
+    // Used by ProximityInfoStateUtils::calculateBeelineSpeedRate()
+    static const int LOOKUP_RADIUS_PERCENTILE;
+    static const int FIRST_POINT_TIME_OFFSET_MILLIS;
+    static const int STRONG_DOUBLE_LETTER_TIME_MILLIS;
+
+    // Used by ProximityInfoStateUtils::calculateNormalizedSquaredDistance()
+    static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR;
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfoParams);
+};
+} // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_PARAMS_H
diff --git a/src/aosp/suggest/core/layout/proximity_info_state.cpp b/src/aosp/suggest/core/layout/proximity_info_state.cpp
new file mode 100644
index 0000000..fbabd92
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info_state.cpp
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "LatinIME: proximity_info_state.cpp"
+
+#include "suggest/core/layout/proximity_info_state.h"
+
+#include <cstring> // for memset() and memcpy()
+#include <sstream> // for debug prints
+#include <vector>
+
+#include "defines.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/layout/proximity_info_state_utils.h"
+#include "utils/char_utils.h"
+
+namespace latinime {
+
+// TODO: Remove the dependency of "isGeometric"
+void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength,
+        const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize,
+        const int *const xCoordinates, const int *const yCoordinates, const int *const times,
+        const int *const pointerIds, const bool isGeometric) {
+    ASSERT(isGeometric || (inputSize < MAX_WORD_LENGTH));
+    mIsContinuousSuggestionPossible = (mHasBeenUpdatedByGeometricInput != isGeometric) ?
+            false : ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
+                    inputSize, xCoordinates, yCoordinates, times, mSampledInputSize,
+                    &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
+    if (DEBUG_DICT) {
+        AKLOGI("isContinuousSuggestionPossible = %s",
+                (mIsContinuousSuggestionPossible ? "true" : "false"));
+    }
+
+    mProximityInfo = proximityInfo;
+    mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
+    mMostCommonKeyWidthSquare = proximityInfo->getMostCommonKeyWidthSquare();
+    mKeyCount = proximityInfo->getKeyCount();
+    mCellHeight = proximityInfo->getCellHeight();
+    mCellWidth = proximityInfo->getCellWidth();
+    mGridHeight = proximityInfo->getGridWidth();
+    mGridWidth = proximityInfo->getGridHeight();
+
+    memset(mInputProximities, 0, sizeof(mInputProximities));
+
+    if (!isGeometric && pointerId == 0) {
+        mProximityInfo->initializeProximities(inputCodes, xCoordinates, yCoordinates,
+                inputSize, mInputProximities);
+    }
+
+    ///////////////////////
+    // Setup touch points
+    int pushTouchPointStartIndex = 0;
+    int lastSavedInputSize = 0;
+    mMaxPointToKeyLength = maxPointToKeyLength;
+    mSampledInputSize = 0;
+    mMostProbableStringProbability = 0.0f;
+
+    if (mIsContinuousSuggestionPossible && mSampledInputIndice.size() > 1) {
+        // Just update difference.
+        // Previous two points are never skipped. Thus, we pop 2 input point data here.
+        pushTouchPointStartIndex = ProximityInfoStateUtils::trimLastTwoTouchPoints(
+                &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledLengthCache,
+                &mSampledInputIndice);
+        lastSavedInputSize = mSampledInputXs.size();
+    } else {
+        // Clear all data.
+        mSampledInputXs.clear();
+        mSampledInputYs.clear();
+        mSampledTimes.clear();
+        mSampledInputIndice.clear();
+        mSampledLengthCache.clear();
+        mSampledNormalizedSquaredLengthCache.clear();
+        mSampledNearKeySets.clear();
+        mSampledSearchKeySets.clear();
+        mSpeedRates.clear();
+        mBeelineSpeedPercentiles.clear();
+        mCharProbabilities.clear();
+        mDirections.clear();
+    }
+
+    if (DEBUG_GEO_FULL) {
+        AKLOGI("Init ProximityInfoState: reused points =  %d, last input size = %d",
+                pushTouchPointStartIndex, lastSavedInputSize);
+    }
+
+    if (xCoordinates && yCoordinates) {
+        mSampledInputSize = ProximityInfoStateUtils::updateTouchPoints(mProximityInfo,
+                mMaxPointToKeyLength, mInputProximities, xCoordinates, yCoordinates, times,
+                pointerIds, inputSize, isGeometric, pointerId,
+                pushTouchPointStartIndex, &mSampledInputXs, &mSampledInputYs, &mSampledTimes,
+                &mSampledLengthCache, &mSampledInputIndice);
+    }
+
+    if (mSampledInputSize > 0 && isGeometric) {
+        mAverageSpeed = ProximityInfoStateUtils::refreshSpeedRates(inputSize, xCoordinates,
+                yCoordinates, times, lastSavedInputSize, mSampledInputSize, &mSampledInputXs,
+                &mSampledInputYs, &mSampledTimes, &mSampledLengthCache, &mSampledInputIndice,
+                &mSpeedRates, &mDirections);
+        ProximityInfoStateUtils::refreshBeelineSpeedRates(mProximityInfo->getMostCommonKeyWidth(),
+                mAverageSpeed, inputSize, xCoordinates, yCoordinates, times, mSampledInputSize,
+                &mSampledInputXs, &mSampledInputYs, &mSampledInputIndice,
+                &mBeelineSpeedPercentiles);
+    }
+
+    if (mSampledInputSize > 0) {
+        ProximityInfoStateUtils::initGeometricDistanceInfos(mProximityInfo, mSampledInputSize,
+                lastSavedInputSize, isGeometric, &mSampledInputXs, &mSampledInputYs,
+                &mSampledNearKeySets, &mSampledNormalizedSquaredLengthCache);
+        if (isGeometric) {
+            // updates probabilities of skipping or mapping each key for all points.
+            ProximityInfoStateUtils::updateAlignPointProbabilities(
+                    mMaxPointToKeyLength, mProximityInfo->getMostCommonKeyWidth(),
+                    mProximityInfo->getKeyCount(), lastSavedInputSize, mSampledInputSize,
+                    &mSampledInputXs, &mSampledInputYs, &mSpeedRates, &mSampledLengthCache,
+                    &mSampledNormalizedSquaredLengthCache, &mSampledNearKeySets,
+                    &mCharProbabilities);
+            ProximityInfoStateUtils::updateSampledSearchKeySets(mProximityInfo,
+                    mSampledInputSize, lastSavedInputSize, &mSampledLengthCache,
+                    &mSampledNearKeySets, &mSampledSearchKeySets,
+                    &mSampledSearchKeyVectors);
+            mMostProbableStringProbability = ProximityInfoStateUtils::getMostProbableString(
+                    mProximityInfo, mSampledInputSize, &mCharProbabilities, mMostProbableString);
+
+        }
+    }
+
+    if (DEBUG_SAMPLING_POINTS) {
+        ProximityInfoStateUtils::dump(isGeometric, inputSize, xCoordinates, yCoordinates,
+                mSampledInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSpeedRates,
+                &mBeelineSpeedPercentiles);
+    }
+    // end
+    ///////////////////////
+
+    mTouchPositionCorrectionEnabled = mSampledInputSize > 0 && mHasTouchPositionCorrectionData
+            && xCoordinates && yCoordinates;
+    if (!isGeometric && pointerId == 0) {
+        ProximityInfoStateUtils::initPrimaryInputWord(
+                inputSize, mInputProximities, mPrimaryInputWord);
+    }
+    if (DEBUG_GEO_FULL) {
+        AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize);
+    }
+    mHasBeenUpdatedByGeometricInput = isGeometric;
+}
+
+// This function basically converts from a length to an edit distance. Accordingly, it's obviously
+// wrong to compare with mMaxPointToKeyLength.
+float ProximityInfoState::getPointToKeyLength(
+        const int inputIndex, const int codePoint) const {
+    const int keyId = mProximityInfo->getKeyIndexOf(codePoint);
+    if (keyId != NOT_AN_INDEX) {
+        const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
+        return min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength);
+    }
+    if (CharUtils::isIntentionalOmissionCodePoint(codePoint)) {
+        return 0.0f;
+    }
+    // If the char is not a key on the keyboard then return the max length.
+    return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+}
+
+float ProximityInfoState::getPointToKeyByIdLength(
+        const int inputIndex, const int keyId) const {
+    return ProximityInfoStateUtils::getPointToKeyByIdLength(mMaxPointToKeyLength,
+            &mSampledNormalizedSquaredLengthCache, mProximityInfo->getKeyCount(), inputIndex,
+            keyId);
+}
+
+// In the following function, c is the current character of the dictionary word currently examined.
+// currentChars is an array containing the keys close to the character the user actually typed at
+// the same position. We want to see if c is in it: if so, then the word contains at that position
+// a character close to what the user typed.
+// What the user typed is actually the first character of the array.
+// proximityIndex is a pointer to the variable where getProximityType returns the index of c
+// in the proximity chars of the input index.
+// Notice : accented characters do not have a proximity list, so they are alone in their list. The
+// non-accented version of the character should be considered "close", but not the other keys close
+// to the non-accented version.
+ProximityType ProximityInfoState::getProximityType(const int index, const int codePoint,
+        const bool checkProximityChars, int *proximityIndex) const {
+    const int *currentCodePoints = getProximityCodePointsAt(index);
+    const int firstCodePoint = currentCodePoints[0];
+    const int baseLowerC = CharUtils::toBaseLowerCase(codePoint);
+
+    // The first char in the array is what user typed. If it matches right away, that means the
+    // user typed that same char for this pos.
+    if (firstCodePoint == baseLowerC || firstCodePoint == codePoint) {
+        return MATCH_CHAR;
+    }
+
+    if (!checkProximityChars) return SUBSTITUTION_CHAR;
+
+    // If the non-accented, lowercased version of that first character matches c, then we have a
+    // non-accented version of the accented character the user typed. Treat it as a close char.
+    if (CharUtils::toBaseLowerCase(firstCodePoint) == baseLowerC) {
+        return PROXIMITY_CHAR;
+    }
+
+    // Not an exact nor an accent-alike match: search the list of close keys
+    int j = 1;
+    while (j < MAX_PROXIMITY_CHARS_SIZE
+            && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+        const bool matched = (currentCodePoints[j] == baseLowerC
+                || currentCodePoints[j] == codePoint);
+        if (matched) {
+            if (proximityIndex) {
+                *proximityIndex = j;
+            }
+            return PROXIMITY_CHAR;
+        }
+        ++j;
+    }
+    if (j < MAX_PROXIMITY_CHARS_SIZE
+            && currentCodePoints[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+        ++j;
+        while (j < MAX_PROXIMITY_CHARS_SIZE
+                && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
+            const bool matched = (currentCodePoints[j] == baseLowerC
+                    || currentCodePoints[j] == codePoint);
+            if (matched) {
+                if (proximityIndex) {
+                    *proximityIndex = j;
+                }
+                return ADDITIONAL_PROXIMITY_CHAR;
+            }
+            ++j;
+        }
+    }
+    // Was not included, signal this as a substitution character.
+    return SUBSTITUTION_CHAR;
+}
+
+ProximityType ProximityInfoState::getProximityTypeG(const int index, const int codePoint) const {
+    if (!isUsed()) {
+        return UNRELATED_CHAR;
+    }
+    const int lowerCodePoint = CharUtils::toLowerCase(codePoint);
+    const int baseLowerCodePoint = CharUtils::toBaseCodePoint(lowerCodePoint);
+    for (int i = 0; i < static_cast<int>(mSampledSearchKeyVectors[index].size()); ++i) {
+        if (mSampledSearchKeyVectors[index][i] == lowerCodePoint
+                || mSampledSearchKeyVectors[index][i] == baseLowerCodePoint) {
+            return MATCH_CHAR;
+        }
+    }
+    return UNRELATED_CHAR;
+}
+
+bool ProximityInfoState::isKeyInSerchKeysAfterIndex(const int index, const int keyId) const {
+    ASSERT(keyId >= 0 && index >= 0 && index < mSampledInputSize);
+    return mSampledSearchKeySets[index].test(keyId);
+}
+
+float ProximityInfoState::getDirection(const int index0, const int index1) const {
+    return ProximityInfoStateUtils::getDirection(
+            &mSampledInputXs, &mSampledInputYs, index0, index1);
+}
+
+float ProximityInfoState::getMostProbableString(int *const codePointBuf) const {
+    memcpy(codePointBuf, mMostProbableString, sizeof(mMostProbableString));
+    return mMostProbableStringProbability;
+}
+
+bool ProximityInfoState::hasSpaceProximity(const int index) const {
+    ASSERT(0 <= index && index < mSampledInputSize);
+    return mProximityInfo->hasSpaceProximity(getInputX(index), getInputY(index));
+}
+
+// Returns a probability of mapping index to keyIndex.
+float ProximityInfoState::getProbability(const int index, const int keyIndex) const {
+    ASSERT(0 <= index && index < mSampledInputSize);
+    hash_map_compat<int, float>::const_iterator it = mCharProbabilities[index].find(keyIndex);
+    if (it != mCharProbabilities[index].end()) {
+        return it->second;
+    }
+    return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+}
+} // namespace latinime
diff --git a/src/aosp/suggest/core/layout/proximity_info_state.h b/src/aosp/suggest/core/layout/proximity_info_state.h
new file mode 100644
index 0000000..c94060f
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info_state.h
@@ -0,0 +1,243 @@
+/*
+ * 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_PROXIMITY_INFO_STATE_H
+#define LATINIME_PROXIMITY_INFO_STATE_H
+
+#include <cstring> // for memset()
+#include <vector>
+
+#include "defines.h"
+#include "suggest/core/layout/proximity_info_params.h"
+#include "suggest/core/layout/proximity_info_state_utils.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+class ProximityInfo;
+
+class ProximityInfoState {
+ public:
+    /////////////////////////////////////////
+    // Defined in proximity_info_state.cpp //
+    /////////////////////////////////////////
+    void initInputParams(const int pointerId, const float maxPointToKeyLength,
+            const ProximityInfo *proximityInfo, const int *const inputCodes,
+            const int inputSize, const int *xCoordinates, const int *yCoordinates,
+            const int *const times, const int *const pointerIds, const bool isGeometric);
+
+    /////////////////////////////////////////
+    // Defined here                        //
+    /////////////////////////////////////////
+    AK_FORCE_INLINE ProximityInfoState()
+            : mProximityInfo(0), mMaxPointToKeyLength(0.0f), mAverageSpeed(0.0f),
+              mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0),
+              mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
+              mIsContinuousSuggestionPossible(false), mHasBeenUpdatedByGeometricInput(false),
+              mSampledInputXs(), mSampledInputYs(), mSampledTimes(), mSampledInputIndice(),
+              mSampledLengthCache(), mBeelineSpeedPercentiles(),
+              mSampledNormalizedSquaredLengthCache(), mSpeedRates(), mDirections(),
+              mCharProbabilities(), mSampledNearKeySets(), mSampledSearchKeySets(),
+              mSampledSearchKeyVectors(), mTouchPositionCorrectionEnabled(false),
+              mSampledInputSize(0), mMostProbableStringProbability(0.0f) {
+        memset(mInputProximities, 0, sizeof(mInputProximities));
+        memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
+        memset(mMostProbableString, 0, sizeof(mMostProbableString));
+    }
+
+    // Non virtual inline destructor -- never inherit this class
+    AK_FORCE_INLINE ~ProximityInfoState() {}
+
+    inline int getPrimaryCodePointAt(const int index) const {
+        return getProximityCodePointsAt(index)[0];
+    }
+
+    inline bool sameAsTyped(const int *word, int length) const {
+        if (length != mSampledInputSize) {
+            return false;
+        }
+        const int *inputProximities = mInputProximities;
+        while (length--) {
+            if (*inputProximities != *word) {
+                return false;
+            }
+            inputProximities += MAX_PROXIMITY_CHARS_SIZE;
+            word++;
+        }
+        return true;
+    }
+
+    AK_FORCE_INLINE bool existsCodePointInProximityAt(const int index, const int c) const {
+        const int *codePoints = getProximityCodePointsAt(index);
+        int i = 0;
+        while (codePoints[i] > 0 && i < MAX_PROXIMITY_CHARS_SIZE) {
+            if (codePoints[i++] == c) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    AK_FORCE_INLINE bool existsAdjacentProximityChars(const int index) const {
+        if (index < 0 || index >= mSampledInputSize) return false;
+        const int currentCodePoint = getPrimaryCodePointAt(index);
+        const int leftIndex = index - 1;
+        if (leftIndex >= 0 && existsCodePointInProximityAt(leftIndex, currentCodePoint)) {
+            return true;
+        }
+        const int rightIndex = index + 1;
+        if (rightIndex < mSampledInputSize
+                && existsCodePointInProximityAt(rightIndex, currentCodePoint)) {
+            return true;
+        }
+        return false;
+    }
+
+    inline const int *getPrimaryInputWord() const {
+        return mPrimaryInputWord;
+    }
+
+    inline bool touchPositionCorrectionEnabled() const {
+        return mTouchPositionCorrectionEnabled;
+    }
+
+    bool isUsed() const {
+        return mSampledInputSize > 0;
+    }
+
+    int size() const {
+        return mSampledInputSize;
+    }
+
+    int getInputX(const int index) const {
+        return mSampledInputXs[index];
+    }
+
+    int getInputY(const int index) const {
+        return mSampledInputYs[index];
+    }
+
+    int getInputIndexOfSampledPoint(const int sampledIndex) const {
+        return mSampledInputIndice[sampledIndex];
+    }
+
+    bool hasSpaceProximity(const int index) const;
+
+    int getLengthCache(const int index) const {
+        return mSampledLengthCache[index];
+    }
+
+    bool isContinuousSuggestionPossible() const {
+        return mIsContinuousSuggestionPossible;
+    }
+
+    // TODO: Rename s/Length/NormalizedSquaredLength/
+    float getPointToKeyByIdLength(const int inputIndex, const int keyId) const;
+    // TODO: Rename s/Length/NormalizedSquaredLength/
+    float getPointToKeyLength(const int inputIndex, const int codePoint) const;
+
+    ProximityType getProximityType(const int index, const int codePoint,
+            const bool checkProximityChars, int *proximityIndex = 0) const;
+
+    ProximityType getProximityTypeG(const int index, const int codePoint) const;
+
+    const std::vector<int> *getSearchKeyVector(const int index) const {
+        return &mSampledSearchKeyVectors[index];
+    }
+
+    float getSpeedRate(const int index) const {
+        return mSpeedRates[index];
+    }
+
+    AK_FORCE_INLINE int getBeelineSpeedPercentile(const int id) const {
+        return mBeelineSpeedPercentiles[id];
+    }
+
+    AK_FORCE_INLINE DoubleLetterLevel getDoubleLetterLevel(const int id) const {
+        const int beelineSpeedRate = getBeelineSpeedPercentile(id);
+        if (beelineSpeedRate == 0) {
+            return A_STRONG_DOUBLE_LETTER;
+        } else if (beelineSpeedRate
+                < ProximityInfoParams::MIN_DOUBLE_LETTER_BEELINE_SPEED_PERCENTILE) {
+            return A_DOUBLE_LETTER;
+        } else {
+            return NOT_A_DOUBLE_LETTER;
+        }
+    }
+
+    float getDirection(const int index) const {
+        return mDirections[index];
+    }
+    // get xy direction
+    float getDirection(const int x, const int y) const;
+
+    float getMostProbableString(int *const codePointBuf) const;
+
+    float getProbability(const int index, const int charCode) const;
+
+    bool isKeyInSerchKeysAfterIndex(const int index, const int keyId) const;
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
+
+    inline const int *getProximityCodePointsAt(const int index) const {
+        return ProximityInfoStateUtils::getProximityCodePointsAt(mInputProximities, index);
+    }
+
+    // const
+    const ProximityInfo *mProximityInfo;
+    float mMaxPointToKeyLength;
+    float mAverageSpeed;
+    bool mHasTouchPositionCorrectionData;
+    int mMostCommonKeyWidthSquare;
+    int mKeyCount;
+    int mCellHeight;
+    int mCellWidth;
+    int mGridHeight;
+    int mGridWidth;
+    bool mIsContinuousSuggestionPossible;
+    bool mHasBeenUpdatedByGeometricInput;
+
+    std::vector<int> mSampledInputXs;
+    std::vector<int> mSampledInputYs;
+    std::vector<int> mSampledTimes;
+    std::vector<int> mSampledInputIndice;
+    std::vector<int> mSampledLengthCache;
+    std::vector<int> mBeelineSpeedPercentiles;
+    std::vector<float> mSampledNormalizedSquaredLengthCache;
+    std::vector<float> mSpeedRates;
+    std::vector<float> mDirections;
+    // probabilities of skipping or mapping to a key for each point.
+    std::vector<hash_map_compat<int, float> > mCharProbabilities;
+    // The vector for the key code set which holds nearby keys for each sampled input point
+    // 1. Used to calculate the probability of the key
+    // 2. Used to calculate mSampledSearchKeySets
+    std::vector<ProximityInfoStateUtils::NearKeycodesSet> mSampledNearKeySets;
+    // The vector for the key code set which holds nearby keys of some trailing sampled input points
+    // for each sampled input point. These nearby keys contain the next characters which can be in
+    // the dictionary. Specifically, currently we are looking for keys nearby trailing sampled
+    // inputs including the current input point.
+    std::vector<ProximityInfoStateUtils::NearKeycodesSet> mSampledSearchKeySets;
+    std::vector<std::vector<int> > mSampledSearchKeyVectors;
+    bool mTouchPositionCorrectionEnabled;
+    int mInputProximities[MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH];
+    int mSampledInputSize;
+    int mPrimaryInputWord[MAX_WORD_LENGTH];
+    float mMostProbableStringProbability;
+    int mMostProbableString[MAX_WORD_LENGTH];
+};
+} // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_STATE_H
diff --git a/src/aosp/suggest/core/layout/proximity_info_state_utils.cpp b/src/aosp/suggest/core/layout/proximity_info_state_utils.cpp
new file mode 100644
index 0000000..e1b3534
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info_state_utils.cpp
@@ -0,0 +1,1045 @@
+/*
+ * 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/core/layout/proximity_info_state_utils.h"
+
+#include <cmath>
+#include <cstring> // for memset()
+#include <sstream> // for debug prints
+#include <vector>
+
+#include "defines.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/layout/proximity_info_params.h"
+
+namespace latinime {
+
+/* static */ int ProximityInfoStateUtils::trimLastTwoTouchPoints(std::vector<int> *sampledInputXs,
+        std::vector<int> *sampledInputYs, std::vector<int> *sampledInputTimes,
+        std::vector<int> *sampledLengthCache, std::vector<int> *sampledInputIndice) {
+    const int nextStartIndex = (*sampledInputIndice)[sampledInputIndice->size() - 2];
+    popInputData(sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
+            sampledInputIndice);
+    popInputData(sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
+            sampledInputIndice);
+    return nextStartIndex;
+}
+
+/* static */ int ProximityInfoStateUtils::updateTouchPoints(
+        const ProximityInfo *const proximityInfo, const int maxPointToKeyLength,
+        const int *const inputProximities, const int *const inputXCoordinates,
+        const int *const inputYCoordinates, const int *const times, const int *const pointerIds,
+        const int inputSize, const bool isGeometric, const int pointerId,
+        const int pushTouchPointStartIndex, std::vector<int> *sampledInputXs,
+        std::vector<int> *sampledInputYs, std::vector<int> *sampledInputTimes,
+        std::vector<int> *sampledLengthCache, std::vector<int> *sampledInputIndice) {
+    if (DEBUG_SAMPLING_POINTS) {
+        if (times) {
+            for (int i = 0; i < inputSize; ++i) {
+                AKLOGI("(%d) x %d, y %d, time %d",
+                        i, inputXCoordinates[i], inputYCoordinates[i], times[i]);
+            }
+        }
+    }
+#ifdef DO_ASSERT_TEST
+    if (times) {
+        for (int i = 0; i < inputSize; ++i) {
+            if (i > 0) {
+                if (times[i] < times[i - 1]) {
+                    AKLOGI("Invalid time sequence. %d, %d", times[i - 1], times[i]);
+                    ASSERT(false);
+                }
+            }
+        }
+    }
+#endif
+    const bool proximityOnly = !isGeometric
+            && (inputXCoordinates[0] < 0 || inputYCoordinates[0] < 0);
+    int lastInputIndex = pushTouchPointStartIndex;
+    for (int i = lastInputIndex; i < inputSize; ++i) {
+        const int pid = pointerIds ? pointerIds[i] : 0;
+        if (pointerId == pid) {
+            lastInputIndex = i;
+        }
+    }
+    if (DEBUG_GEO_FULL) {
+        AKLOGI("Init ProximityInfoState: last input index = %d", lastInputIndex);
+    }
+    // Working space to save near keys distances for current, prev and prevprev input point.
+    NearKeysDistanceMap nearKeysDistances[3];
+    // These pointers are swapped for each inputs points.
+    NearKeysDistanceMap *currentNearKeysDistances = &nearKeysDistances[0];
+    NearKeysDistanceMap *prevNearKeysDistances = &nearKeysDistances[1];
+    NearKeysDistanceMap *prevPrevNearKeysDistances = &nearKeysDistances[2];
+    // "sumAngle" is accumulated by each angle of input points. And when "sumAngle" exceeds
+    // the threshold we save that point, reset sumAngle. This aims to keep the figure of
+    // the curve.
+    float sumAngle = 0.0f;
+
+    for (int i = pushTouchPointStartIndex; i <= lastInputIndex; ++i) {
+        // Assuming pointerId == 0 if pointerIds is null.
+        const int pid = pointerIds ? pointerIds[i] : 0;
+        if (DEBUG_GEO_FULL) {
+            AKLOGI("Init ProximityInfoState: (%d)PID = %d", i, pid);
+        }
+        if (pointerId == pid) {
+            const int c = isGeometric ?
+                    NOT_A_COORDINATE : getPrimaryCodePointAt(inputProximities, i);
+            const int x = proximityOnly ? NOT_A_COORDINATE : inputXCoordinates[i];
+            const int y = proximityOnly ? NOT_A_COORDINATE : inputYCoordinates[i];
+            const int time = times ? times[i] : -1;
+
+            if (i > 1) {
+                const float prevAngle = GeometryUtils::getAngle(
+                        inputXCoordinates[i - 2], inputYCoordinates[i - 2],
+                        inputXCoordinates[i - 1], inputYCoordinates[i - 1]);
+                const float currentAngle = GeometryUtils::getAngle(
+                        inputXCoordinates[i - 1], inputYCoordinates[i - 1], x, y);
+                sumAngle += GeometryUtils::getAngleDiff(prevAngle, currentAngle);
+            }
+
+            if (pushTouchPoint(proximityInfo, maxPointToKeyLength, i, c, x, y, time,
+                    isGeometric, isGeometric /* doSampling */, i == lastInputIndex,
+                    sumAngle, currentNearKeysDistances, prevNearKeysDistances,
+                    prevPrevNearKeysDistances, sampledInputXs, sampledInputYs, sampledInputTimes,
+                    sampledLengthCache, sampledInputIndice)) {
+                // Previous point information was popped.
+                NearKeysDistanceMap *tmp = prevNearKeysDistances;
+                prevNearKeysDistances = currentNearKeysDistances;
+                currentNearKeysDistances = tmp;
+            } else {
+                NearKeysDistanceMap *tmp = prevPrevNearKeysDistances;
+                prevPrevNearKeysDistances = prevNearKeysDistances;
+                prevNearKeysDistances = currentNearKeysDistances;
+                currentNearKeysDistances = tmp;
+                sumAngle = 0.0f;
+            }
+        }
+    }
+    return sampledInputXs->size();
+}
+
+/* static */ const int *ProximityInfoStateUtils::getProximityCodePointsAt(
+        const int *const inputProximities, const int index) {
+    return inputProximities + (index * MAX_PROXIMITY_CHARS_SIZE);
+}
+
+/* static */ int ProximityInfoStateUtils::getPrimaryCodePointAt(const int *const inputProximities,
+        const int index) {
+    return getProximityCodePointsAt(inputProximities, index)[0];
+}
+
+/* static */ void ProximityInfoStateUtils::initPrimaryInputWord(const int inputSize,
+        const int *const inputProximities, int *primaryInputWord) {
+    memset(primaryInputWord, 0, sizeof(primaryInputWord[0]) * MAX_WORD_LENGTH);
+    for (int i = 0; i < inputSize; ++i) {
+        primaryInputWord[i] = getPrimaryCodePointAt(inputProximities, i);
+    }
+}
+
+/* static */ float ProximityInfoStateUtils::calculateSquaredDistanceFromSweetSpotCenter(
+        const ProximityInfo *const proximityInfo, const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs, const int keyIndex, const int inputIndex) {
+    const float sweetSpotCenterX = proximityInfo->getSweetSpotCenterXAt(keyIndex);
+    const float sweetSpotCenterY = proximityInfo->getSweetSpotCenterYAt(keyIndex);
+    const float inputX = static_cast<float>((*sampledInputXs)[inputIndex]);
+    const float inputY = static_cast<float>((*sampledInputYs)[inputIndex]);
+    return GeometryUtils::SQUARE_FLOAT(inputX - sweetSpotCenterX)
+            + GeometryUtils::SQUARE_FLOAT(inputY - sweetSpotCenterY);
+}
+
+/* static */ float ProximityInfoStateUtils::calculateNormalizedSquaredDistance(
+        const ProximityInfo *const proximityInfo, const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs, const int keyIndex, const int inputIndex) {
+    if (keyIndex == NOT_AN_INDEX) {
+        return ProximityInfoParams::NOT_A_DISTANCE_FLOAT;
+    }
+    if (!proximityInfo->hasSweetSpotData(keyIndex)) {
+        return ProximityInfoParams::NOT_A_DISTANCE_FLOAT;
+    }
+    if (NOT_A_COORDINATE == (*sampledInputXs)[inputIndex]) {
+        return ProximityInfoParams::NOT_A_DISTANCE_FLOAT;
+    }
+    const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(proximityInfo,
+            sampledInputXs, sampledInputYs, keyIndex, inputIndex);
+    const float squaredRadius = GeometryUtils::SQUARE_FLOAT(
+            proximityInfo->getSweetSpotRadiiAt(keyIndex));
+    return squaredDistance / squaredRadius;
+}
+
+/* static */ void ProximityInfoStateUtils::initGeometricDistanceInfos(
+        const ProximityInfo *const proximityInfo, const int sampledInputSize,
+        const int lastSavedInputSize, const bool isGeometric,
+        const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs,
+        std::vector<NearKeycodesSet> *sampledNearKeySets,
+        std::vector<float> *sampledNormalizedSquaredLengthCache) {
+    sampledNearKeySets->resize(sampledInputSize);
+    const int keyCount = proximityInfo->getKeyCount();
+    sampledNormalizedSquaredLengthCache->resize(sampledInputSize * keyCount);
+    for (int i = lastSavedInputSize; i < sampledInputSize; ++i) {
+        (*sampledNearKeySets)[i].reset();
+        for (int k = 0; k < keyCount; ++k) {
+            const int index = i * keyCount + k;
+            const int x = (*sampledInputXs)[i];
+            const int y = (*sampledInputYs)[i];
+            const float normalizedSquaredDistance =
+                    proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(
+                            k, x, y, isGeometric);
+            (*sampledNormalizedSquaredLengthCache)[index] = normalizedSquaredDistance;
+            if (normalizedSquaredDistance
+                    < ProximityInfoParams::NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD) {
+                (*sampledNearKeySets)[i][k] = true;
+            }
+        }
+    }
+}
+
+/* static */ void ProximityInfoStateUtils::popInputData(std::vector<int> *sampledInputXs,
+        std::vector<int> *sampledInputYs, std::vector<int> *sampledInputTimes,
+        std::vector<int> *sampledLengthCache, std::vector<int> *sampledInputIndice) {
+    sampledInputXs->pop_back();
+    sampledInputYs->pop_back();
+    sampledInputTimes->pop_back();
+    sampledLengthCache->pop_back();
+    sampledInputIndice->pop_back();
+}
+
+/* static */ float ProximityInfoStateUtils::refreshSpeedRates(const int inputSize,
+        const int *const xCoordinates, const int *const yCoordinates, const int *const times,
+        const int lastSavedInputSize, const int sampledInputSize,
+        const std::vector<int> *const sampledInputXs, const std::vector<int> *const sampledInputYs,
+        const std::vector<int> *const sampledInputTimes,
+        const std::vector<int> *const sampledLengthCache,
+        const std::vector<int> *const sampledInputIndice, std::vector<float> *sampledSpeedRates,
+        std::vector<float> *sampledDirections) {
+    // Relative speed calculation.
+    const int sumDuration = sampledInputTimes->back() - sampledInputTimes->front();
+    const int sumLength = sampledLengthCache->back() - sampledLengthCache->front();
+    const float averageSpeed = static_cast<float>(sumLength) / static_cast<float>(sumDuration);
+    sampledSpeedRates->resize(sampledInputSize);
+    for (int i = lastSavedInputSize; i < sampledInputSize; ++i) {
+        const int index = (*sampledInputIndice)[i];
+        int length = 0;
+        int duration = 0;
+
+        // Calculate velocity by using distances and durations of
+        // ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION points for both forward and
+        // backward.
+        const int forwardNumPoints = min(inputSize - 1,
+                index + ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION);
+        for (int j = index; j < forwardNumPoints; ++j) {
+            if (i < sampledInputSize - 1 && j >= (*sampledInputIndice)[i + 1]) {
+                break;
+            }
+            length += GeometryUtils::getDistanceInt(xCoordinates[j], yCoordinates[j],
+                    xCoordinates[j + 1], yCoordinates[j + 1]);
+            duration += times[j + 1] - times[j];
+        }
+        const int backwardNumPoints = max(0,
+                index - ProximityInfoParams::NUM_POINTS_FOR_SPEED_CALCULATION);
+        for (int j = index - 1; j >= backwardNumPoints; --j) {
+            if (i > 0 && j < (*sampledInputIndice)[i - 1]) {
+                break;
+            }
+            // TODO: use mSampledLengthCache instead?
+            length += GeometryUtils::getDistanceInt(xCoordinates[j], yCoordinates[j],
+                    xCoordinates[j + 1], yCoordinates[j + 1]);
+            duration += times[j + 1] - times[j];
+        }
+        if (duration == 0 || sumDuration == 0) {
+            // Cannot calculate speed; thus, it gives an average value (1.0);
+            (*sampledSpeedRates)[i] = 1.0f;
+        } else {
+            const float speed = static_cast<float>(length) / static_cast<float>(duration);
+            (*sampledSpeedRates)[i] = speed / averageSpeed;
+        }
+    }
+
+    // Direction calculation.
+    sampledDirections->resize(sampledInputSize - 1);
+    for (int i = max(0, lastSavedInputSize - 1); i < sampledInputSize - 1; ++i) {
+        (*sampledDirections)[i] = getDirection(sampledInputXs, sampledInputYs, i, i + 1);
+    }
+    return averageSpeed;
+}
+
+/* static */ void ProximityInfoStateUtils::refreshBeelineSpeedRates(const int mostCommonKeyWidth,
+        const float averageSpeed, const int inputSize, const int *const xCoordinates,
+        const int *const yCoordinates, const int *times, const int sampledInputSize,
+        const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs, const std::vector<int> *const inputIndice,
+        std::vector<int> *beelineSpeedPercentiles) {
+    if (DEBUG_SAMPLING_POINTS) {
+        AKLOGI("--- refresh beeline speed rates");
+    }
+    beelineSpeedPercentiles->resize(sampledInputSize);
+    for (int i = 0; i < sampledInputSize; ++i) {
+        (*beelineSpeedPercentiles)[i] = static_cast<int>(calculateBeelineSpeedRate(
+                mostCommonKeyWidth, averageSpeed, i, inputSize, xCoordinates, yCoordinates, times,
+                sampledInputSize, sampledInputXs, sampledInputYs, inputIndice) * MAX_PERCENTILE);
+    }
+}
+
+/* static */float ProximityInfoStateUtils::getDirection(
+        const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs, const int index0, const int index1) {
+    ASSERT(sampledInputXs && sampledInputYs);
+    const int sampledInputSize =sampledInputXs->size();
+    if (index0 < 0 || index0 > sampledInputSize - 1) {
+        return 0.0f;
+    }
+    if (index1 < 0 || index1 > sampledInputSize - 1) {
+        return 0.0f;
+    }
+    const int x1 = (*sampledInputXs)[index0];
+    const int y1 = (*sampledInputYs)[index0];
+    const int x2 = (*sampledInputXs)[index1];
+    const int y2 = (*sampledInputYs)[index1];
+    return GeometryUtils::getAngle(x1, y1, x2, y2);
+}
+
+// Calculating point to key distance for all near keys and returning the distance between
+// the given point and the nearest key position.
+/* static */ float ProximityInfoStateUtils::updateNearKeysDistances(
+        const ProximityInfo *const proximityInfo, const float maxPointToKeyLength, const int x,
+        const int y, const bool isGeometric, NearKeysDistanceMap *const currentNearKeysDistances) {
+    currentNearKeysDistances->clear();
+    const int keyCount = proximityInfo->getKeyCount();
+    float nearestKeyDistance = maxPointToKeyLength;
+    for (int k = 0; k < keyCount; ++k) {
+        const float dist = proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y,
+                isGeometric);
+        if (dist < ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_DISTANCE) {
+            currentNearKeysDistances->insert(std::pair<int, float>(k, dist));
+        }
+        if (nearestKeyDistance > dist) {
+            nearestKeyDistance = dist;
+        }
+    }
+    return nearestKeyDistance;
+}
+
+// Check if previous point is at local minimum position to near keys.
+/* static */ bool ProximityInfoStateUtils::isPrevLocalMin(
+        const NearKeysDistanceMap *const currentNearKeysDistances,
+        const NearKeysDistanceMap *const prevNearKeysDistances,
+        const NearKeysDistanceMap *const prevPrevNearKeysDistances) {
+    for (NearKeysDistanceMap::const_iterator it = prevNearKeysDistances->begin();
+            it != prevNearKeysDistances->end(); ++it) {
+        NearKeysDistanceMap::const_iterator itPP = prevPrevNearKeysDistances->find(it->first);
+        NearKeysDistanceMap::const_iterator itC = currentNearKeysDistances->find(it->first);
+        const bool isPrevPrevNear = (itPP == prevPrevNearKeysDistances->end()
+                || itPP->second > it->second + ProximityInfoParams::MARGIN_FOR_PREV_LOCAL_MIN);
+        const bool isCurrentNear = (itC == currentNearKeysDistances->end()
+                || itC->second > it->second + ProximityInfoParams::MARGIN_FOR_PREV_LOCAL_MIN);
+        if (isPrevPrevNear && isCurrentNear) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// Calculating a point score that indicates usefulness of the point.
+/* static */ float ProximityInfoStateUtils::getPointScore(const int mostCommonKeyWidth,
+        const int x, const int y, const int time, const bool lastPoint, const float nearest,
+        const float sumAngle, const NearKeysDistanceMap *const currentNearKeysDistances,
+        const NearKeysDistanceMap *const prevNearKeysDistances,
+        const NearKeysDistanceMap *const prevPrevNearKeysDistances,
+        std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs) {
+    const size_t size = sampledInputXs->size();
+    // If there is only one point, add this point. Besides, if the previous point's distance map
+    // is empty, we re-compute nearby keys distances from the current point.
+    // Note that the current point is the first point in the incremental input that needs to
+    // be re-computed.
+    if (size <= 1 || prevNearKeysDistances->empty()) {
+        return 0.0f;
+    }
+
+    const int baseSampleRate = mostCommonKeyWidth;
+    const int distPrev = GeometryUtils::getDistanceInt(sampledInputXs->back(),
+            sampledInputYs->back(), (*sampledInputXs)[size - 2],
+            (*sampledInputYs)[size - 2]) * ProximityInfoParams::DISTANCE_BASE_SCALE;
+    float score = 0.0f;
+
+    // Location
+    if (!isPrevLocalMin(currentNearKeysDistances, prevNearKeysDistances,
+        prevPrevNearKeysDistances)) {
+        score += ProximityInfoParams::NOT_LOCALMIN_DISTANCE_SCORE;
+    } else if (nearest < ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_POINT_SCORE) {
+        // Promote points nearby keys
+        score += ProximityInfoParams::LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE;
+    }
+    // Angle
+    const float angle1 = GeometryUtils::getAngle(x, y, sampledInputXs->back(),
+            sampledInputYs->back());
+    const float angle2 = GeometryUtils::getAngle(sampledInputXs->back(), sampledInputYs->back(),
+            (*sampledInputXs)[size - 2], (*sampledInputYs)[size - 2]);
+    const float angleDiff = GeometryUtils::getAngleDiff(angle1, angle2);
+
+    // Save corner
+    if (distPrev > baseSampleRate * ProximityInfoParams::CORNER_CHECK_DISTANCE_THRESHOLD_SCALE
+            && (sumAngle > ProximityInfoParams::CORNER_SUM_ANGLE_THRESHOLD
+                    || angleDiff > ProximityInfoParams::CORNER_ANGLE_THRESHOLD_FOR_POINT_SCORE)) {
+        score += ProximityInfoParams::CORNER_SCORE;
+    }
+    return score;
+}
+
+// Sampling touch point and pushing information to vectors.
+// Returning if previous point is popped or not.
+/* static */ bool ProximityInfoStateUtils::pushTouchPoint(const ProximityInfo *const proximityInfo,
+        const int maxPointToKeyLength, const int inputIndex, const int nodeCodePoint, int x, int y,
+        const int time, const bool isGeometric, const bool doSampling,
+        const bool isLastPoint, const float sumAngle,
+        NearKeysDistanceMap *const currentNearKeysDistances,
+        const NearKeysDistanceMap *const prevNearKeysDistances,
+        const NearKeysDistanceMap *const prevPrevNearKeysDistances,
+        std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
+        std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
+        std::vector<int> *sampledInputIndice) {
+    const int mostCommonKeyWidth = proximityInfo->getMostCommonKeyWidth();
+
+    size_t size = sampledInputXs->size();
+    bool popped = false;
+    if (nodeCodePoint < 0 && doSampling) {
+        const float nearest = updateNearKeysDistances(proximityInfo, maxPointToKeyLength, x, y,
+                isGeometric, currentNearKeysDistances);
+        const float score = getPointScore(mostCommonKeyWidth, x, y, time, isLastPoint, nearest,
+                sumAngle, currentNearKeysDistances, prevNearKeysDistances,
+                prevPrevNearKeysDistances, sampledInputXs, sampledInputYs);
+        if (score < 0) {
+            // Pop previous point because it would be useless.
+            popInputData(sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
+                    sampledInputIndice);
+            size = sampledInputXs->size();
+            popped = true;
+        } else {
+            popped = false;
+        }
+        // Check if the last point should be skipped.
+        if (isLastPoint && size > 0) {
+            if (GeometryUtils::getDistanceInt(x, y, sampledInputXs->back(), sampledInputYs->back())
+                    * ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE < mostCommonKeyWidth) {
+                // This point is not used because it's too close to the previous point.
+                if (DEBUG_GEO_FULL) {
+                    AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %d, "
+                           "width = %d", size, x, y, sampledInputXs->back(),
+                           sampledInputYs->back(), GeometryUtils::getDistanceInt(
+                                   x, y, sampledInputXs->back(), sampledInputYs->back()),
+                           mostCommonKeyWidth
+                                   / ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE);
+                }
+                return popped;
+            }
+        }
+    }
+
+    if (nodeCodePoint >= 0 && (x < 0 || y < 0)) {
+        const int keyId = proximityInfo->getKeyIndexOf(nodeCodePoint);
+        if (keyId >= 0) {
+            x = proximityInfo->getKeyCenterXOfKeyIdG(keyId, NOT_AN_INDEX, isGeometric);
+            y = proximityInfo->getKeyCenterYOfKeyIdG(keyId, NOT_AN_INDEX, isGeometric);
+        }
+    }
+
+    // Pushing point information.
+    if (size > 0) {
+        sampledLengthCache->push_back(
+                sampledLengthCache->back() + GeometryUtils::getDistanceInt(
+                        x, y, sampledInputXs->back(), sampledInputYs->back()));
+    } else {
+        sampledLengthCache->push_back(0);
+    }
+    sampledInputXs->push_back(x);
+    sampledInputYs->push_back(y);
+    sampledInputTimes->push_back(time);
+    sampledInputIndice->push_back(inputIndex);
+    if (DEBUG_GEO_FULL) {
+        AKLOGI("pushTouchPoint: x = %03d, y = %03d, time = %d, index = %d, popped ? %01d",
+                x, y, time, inputIndex, popped);
+    }
+    return popped;
+}
+
+/* static */ float ProximityInfoStateUtils::calculateBeelineSpeedRate(const int mostCommonKeyWidth,
+        const float averageSpeed, const int id, const int inputSize, const int *const xCoordinates,
+        const int *const yCoordinates, const int *times, const int sampledInputSize,
+        const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs,
+        const std::vector<int> *const sampledInputIndices) {
+    if (sampledInputSize <= 0 || averageSpeed < 0.001f) {
+        if (DEBUG_SAMPLING_POINTS) {
+            AKLOGI("--- invalid state: cancel. size = %d, ave = %f",
+                    sampledInputSize, averageSpeed);
+        }
+        return 1.0f;
+    }
+    const int lookupRadius = mostCommonKeyWidth
+            * ProximityInfoParams::LOOKUP_RADIUS_PERCENTILE / MAX_PERCENTILE;
+    const int x0 = (*sampledInputXs)[id];
+    const int y0 = (*sampledInputYs)[id];
+    const int actualInputIndex = (*sampledInputIndices)[id];
+    int tempTime = 0;
+    int tempBeelineDistance = 0;
+    int start = actualInputIndex;
+    // lookup forward
+    while (start > 0 && tempBeelineDistance < lookupRadius) {
+        tempTime += times[start] - times[start - 1];
+        --start;
+        tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[start],
+                yCoordinates[start]);
+    }
+    // Exclusive unless this is an edge point
+    if (start > 0 && start < actualInputIndex) {
+        ++start;
+    }
+    tempTime= 0;
+    tempBeelineDistance = 0;
+    int end = actualInputIndex;
+    // lookup backward
+    while (end < (inputSize - 1) && tempBeelineDistance < lookupRadius) {
+        tempTime += times[end + 1] - times[end];
+        ++end;
+        tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[end],
+                yCoordinates[end]);
+    }
+    // Exclusive unless this is an edge point
+    if (end > actualInputIndex && end < (inputSize - 1)) {
+        --end;
+    }
+
+    if (start >= end) {
+        if (DEBUG_DOUBLE_LETTER) {
+            AKLOGI("--- double letter: start == end %d", start);
+        }
+        return 1.0f;
+    }
+
+    const int x2 = xCoordinates[start];
+    const int y2 = yCoordinates[start];
+    const int x3 = xCoordinates[end];
+    const int y3 = yCoordinates[end];
+    const int beelineDistance = GeometryUtils::getDistanceInt(x2, y2, x3, y3);
+    int adjustedStartTime = times[start];
+    if (start == 0 && actualInputIndex == 0 && inputSize > 1) {
+        adjustedStartTime += ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS;
+    }
+    int adjustedEndTime = times[end];
+    if (end == (inputSize - 1) && inputSize > 1) {
+        adjustedEndTime -= ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS;
+    }
+    const int time = adjustedEndTime - adjustedStartTime;
+    if (time <= 0) {
+        return 1.0f;
+    }
+
+    if (time >= ProximityInfoParams::STRONG_DOUBLE_LETTER_TIME_MILLIS){
+        return 0.0f;
+    }
+    if (DEBUG_DOUBLE_LETTER) {
+        AKLOGI("--- (%d, %d) double letter: start = %d, end = %d, dist = %d, time = %d,"
+                " speed = %f, ave = %f, val = %f, start time = %d, end time = %d",
+                id, (*sampledInputIndices)[id], start, end, beelineDistance, time,
+                (static_cast<float>(beelineDistance) / static_cast<float>(time)), averageSpeed,
+                ((static_cast<float>(beelineDistance) / static_cast<float>(time))
+                        / averageSpeed), adjustedStartTime, adjustedEndTime);
+    }
+    // Offset 1%
+    // TODO: Detect double letter more smartly
+    return 0.01f + static_cast<float>(beelineDistance) / static_cast<float>(time) / averageSpeed;
+}
+
+/* static */ float ProximityInfoStateUtils::getPointAngle(
+        const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs, const int index) {
+    if (!sampledInputXs || !sampledInputYs) {
+        return 0.0f;
+    }
+    const int sampledInputSize = sampledInputXs->size();
+    if (index <= 0 || index >= sampledInputSize - 1) {
+        return 0.0f;
+    }
+    const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index - 1, index);
+    const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index, index + 1);
+    const float directionDiff = GeometryUtils::getAngleDiff(previousDirection, nextDirection);
+    return directionDiff;
+}
+
+/* static */ float ProximityInfoStateUtils::getPointsAngle(
+        const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs,
+        const int index0, const int index1, const int index2) {
+    if (!sampledInputXs || !sampledInputYs) {
+        return 0.0f;
+    }
+    const int sampledInputSize = sampledInputXs->size();
+    if (index0 < 0 || index0 > sampledInputSize - 1) {
+        return 0.0f;
+    }
+    if (index1 < 0 || index1 > sampledInputSize - 1) {
+        return 0.0f;
+    }
+    if (index2 < 0 || index2 > sampledInputSize - 1) {
+        return 0.0f;
+    }
+    const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index0, index1);
+    const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index1, index2);
+    return GeometryUtils::getAngleDiff(previousDirection, nextDirection);
+}
+
+// This function basically converts from a length to an edit distance. Accordingly, it's obviously
+// wrong to compare with mMaxPointToKeyLength.
+/* static */ float ProximityInfoStateUtils::getPointToKeyByIdLength(const float maxPointToKeyLength,
+        const std::vector<float> *const sampledNormalizedSquaredLengthCache, const int keyCount,
+        const int inputIndex, const int keyId) {
+    if (keyId != NOT_AN_INDEX) {
+        const int index = inputIndex * keyCount + keyId;
+        return min((*sampledNormalizedSquaredLengthCache)[index], maxPointToKeyLength);
+    }
+    // If the char is not a key on the keyboard then return the max length.
+    return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+}
+
+// Updates probabilities of aligning to some keys and skipping.
+// Word suggestion should be based on this probabilities.
+/* static */ void ProximityInfoStateUtils::updateAlignPointProbabilities(
+        const float maxPointToKeyLength, const int mostCommonKeyWidth, const int keyCount,
+        const int start, const int sampledInputSize, const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs,
+        const std::vector<float> *const sampledSpeedRates,
+        const std::vector<int> *const sampledLengthCache,
+        const std::vector<float> *const sampledNormalizedSquaredLengthCache,
+        std::vector<NearKeycodesSet> *sampledNearKeySets,
+        std::vector<hash_map_compat<int, float> > *charProbabilities) {
+    charProbabilities->resize(sampledInputSize);
+    // Calculates probabilities of using a point as a correlated point with the character
+    // for each point.
+    for (int i = start; i < sampledInputSize; ++i) {
+        (*charProbabilities)[i].clear();
+        // First, calculates skip probability. Starts from MAX_SKIP_PROBABILITY.
+        // Note that all values that are multiplied to this probability should be in [0.0, 1.0];
+        float skipProbability = ProximityInfoParams::MAX_SKIP_PROBABILITY;
+
+        const float currentAngle = getPointAngle(sampledInputXs, sampledInputYs, i);
+        const float speedRate = (*sampledSpeedRates)[i];
+
+        float nearestKeyDistance = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+        for (int j = 0; j < keyCount; ++j) {
+            if ((*sampledNearKeySets)[i].test(j)) {
+                const float distance = getPointToKeyByIdLength(
+                        maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount, i, j);
+                if (distance < nearestKeyDistance) {
+                    nearestKeyDistance = distance;
+                }
+            }
+        }
+
+        if (i == 0) {
+            skipProbability *= min(1.0f,
+                    nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
+                            + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
+            // Promote the first point
+            skipProbability *= ProximityInfoParams::SKIP_FIRST_POINT_PROBABILITY;
+        } else if (i == sampledInputSize - 1) {
+            skipProbability *= min(1.0f,
+                    nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT_FOR_LAST
+                            + ProximityInfoParams::NEAREST_DISTANCE_BIAS_FOR_LAST);
+            // Promote the last point
+            skipProbability *= ProximityInfoParams::SKIP_LAST_POINT_PROBABILITY;
+        } else {
+            // If the current speed is relatively slower than adjacent keys, we promote this point.
+            if ((*sampledSpeedRates)[i - 1] - ProximityInfoParams::SPEED_MARGIN > speedRate
+                    && speedRate
+                            < (*sampledSpeedRates)[i + 1] - ProximityInfoParams::SPEED_MARGIN) {
+                if (currentAngle < ProximityInfoParams::CORNER_ANGLE_THRESHOLD) {
+                    skipProbability *= min(1.0f, speedRate
+                            * ProximityInfoParams::SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY);
+                } else {
+                    // If the angle is small enough, we promote this point more. (e.g. pit vs put)
+                    skipProbability *= min(1.0f,
+                            speedRate * ProximityInfoParams::SPEED_WEIGHT_FOR_SKIP_PROBABILITY
+                                    + ProximityInfoParams::MIN_SPEED_RATE_FOR_SKIP_PROBABILITY);
+                }
+            }
+
+            skipProbability *= min(1.0f,
+                    speedRate * nearestKeyDistance * ProximityInfoParams::NEAREST_DISTANCE_WEIGHT
+                            + ProximityInfoParams::NEAREST_DISTANCE_BIAS);
+
+            // Adjusts skip probability by a rate depending on angle.
+            // ANGLE_RATE of skipProbability is adjusted by current angle.
+            skipProbability *= (M_PI_F - currentAngle) / M_PI_F * ProximityInfoParams::ANGLE_WEIGHT
+                    + (1.0f - ProximityInfoParams::ANGLE_WEIGHT);
+            if (currentAngle > ProximityInfoParams::DEEP_CORNER_ANGLE_THRESHOLD) {
+                skipProbability *= ProximityInfoParams::SKIP_DEEP_CORNER_PROBABILITY;
+            }
+            // We assume the angle of this point is the angle for point[i], point[i - 2]
+            // and point[i - 3]. The reason why we don't use the angle for point[i], point[i - 1]
+            // and point[i - 2] is this angle can be more affected by the noise.
+            const float prevAngle = getPointsAngle(sampledInputXs, sampledInputYs, i, i - 2, i - 3);
+            if (i >= 3 && prevAngle < ProximityInfoParams::STRAIGHT_ANGLE_THRESHOLD
+                    && currentAngle > ProximityInfoParams::CORNER_ANGLE_THRESHOLD) {
+                skipProbability *= ProximityInfoParams::SKIP_CORNER_PROBABILITY;
+            }
+        }
+
+        // probabilities must be in [0.0, ProximityInfoParams::MAX_SKIP_PROBABILITY];
+        ASSERT(skipProbability >= 0.0f);
+        ASSERT(skipProbability <= ProximityInfoParams::MAX_SKIP_PROBABILITY);
+        (*charProbabilities)[i][NOT_AN_INDEX] = skipProbability;
+
+        // Second, calculates key probabilities by dividing the rest probability
+        // (1.0f - skipProbability).
+        const float inputCharProbability = 1.0f - skipProbability;
+
+        const float speedxAngleRate = min(speedRate * currentAngle / M_PI_F
+                * ProximityInfoParams::SPEEDxANGLE_WEIGHT_FOR_STANDARD_DEVIATION,
+                        ProximityInfoParams::MAX_SPEEDxANGLE_RATE_FOR_STANDARD_DEVIATION);
+        const float speedxNearestKeyDistanceRate = min(speedRate * nearestKeyDistance
+                * ProximityInfoParams::SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DEVIATION,
+                        ProximityInfoParams::MAX_SPEEDxNEAREST_RATE_FOR_STANDARD_DEVIATION);
+        const float sigma = speedxAngleRate + speedxNearestKeyDistanceRate
+                + ProximityInfoParams::MIN_STANDARD_DEVIATION;
+
+        ProximityInfoUtils::NormalDistribution
+                distribution(ProximityInfoParams::CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION, sigma);
+        // Summing up probability densities of all near keys.
+        float sumOfProbabilityDensities = 0.0f;
+        for (int j = 0; j < keyCount; ++j) {
+            if ((*sampledNearKeySets)[i].test(j)) {
+                float distance = sqrtf(getPointToKeyByIdLength(
+                        maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount, i, j));
+                if (i == 0 && i != sampledInputSize - 1) {
+                    // For the first point, weighted average of distances from first point and the
+                    // next point to the key is used as a point to key distance.
+                    const float nextDistance = sqrtf(getPointToKeyByIdLength(
+                            maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount,
+                            i + 1, j));
+                    if (nextDistance < distance) {
+                        // The distance of the first point tends to bigger than continuing
+                        // points because the first touch by the user can be sloppy.
+                        // So we promote the first point if the distance of that point is larger
+                        // than the distance of the next point.
+                        distance = (distance
+                                + nextDistance * ProximityInfoParams::NEXT_DISTANCE_WEIGHT)
+                                        / (1.0f + ProximityInfoParams::NEXT_DISTANCE_WEIGHT);
+                    }
+                } else if (i != 0 && i == sampledInputSize - 1) {
+                    // For the first point, weighted average of distances from last point and
+                    // the previous point to the key is used as a point to key distance.
+                    const float previousDistance = sqrtf(getPointToKeyByIdLength(
+                            maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount,
+                            i - 1, j));
+                    if (previousDistance < distance) {
+                        // The distance of the last point tends to bigger than continuing points
+                        // because the last touch by the user can be sloppy. So we promote the
+                        // last point if the distance of that point is larger than the distance of
+                        // the previous point.
+                        distance = (distance
+                                + previousDistance * ProximityInfoParams::PREV_DISTANCE_WEIGHT)
+                                        / (1.0f + ProximityInfoParams::PREV_DISTANCE_WEIGHT);
+                    }
+                }
+                // TODO: Promote the first point when the extended line from the next input is near
+                // from a key. Also, promote the last point as well.
+                sumOfProbabilityDensities += distribution.getProbabilityDensity(distance);
+            }
+        }
+
+        // Split the probability of an input point to keys that are close to the input point.
+        for (int j = 0; j < keyCount; ++j) {
+            if ((*sampledNearKeySets)[i].test(j)) {
+                float distance = sqrtf(getPointToKeyByIdLength(
+                        maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount, i, j));
+                if (i == 0 && i != sampledInputSize - 1) {
+                    // For the first point, weighted average of distances from the first point and
+                    // the next point to the key is used as a point to key distance.
+                    const float prevDistance = sqrtf(getPointToKeyByIdLength(
+                            maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount,
+                            i + 1, j));
+                    if (prevDistance < distance) {
+                        distance = (distance
+                                + prevDistance * ProximityInfoParams::NEXT_DISTANCE_WEIGHT)
+                                        / (1.0f + ProximityInfoParams::NEXT_DISTANCE_WEIGHT);
+                    }
+                } else if (i != 0 && i == sampledInputSize - 1) {
+                    // For the first point, weighted average of distances from last point and
+                    // the previous point to the key is used as a point to key distance.
+                    const float prevDistance = sqrtf(getPointToKeyByIdLength(
+                            maxPointToKeyLength, sampledNormalizedSquaredLengthCache, keyCount,
+                            i - 1, j));
+                    if (prevDistance < distance) {
+                        distance = (distance
+                                + prevDistance * ProximityInfoParams::PREV_DISTANCE_WEIGHT)
+                                        / (1.0f + ProximityInfoParams::PREV_DISTANCE_WEIGHT);
+                    }
+                }
+                const float probabilityDensity = distribution.getProbabilityDensity(distance);
+                const float probability = inputCharProbability * probabilityDensity
+                        / sumOfProbabilityDensities;
+                (*charProbabilities)[i][j] = probability;
+            }
+        }
+    }
+
+    if (DEBUG_POINTS_PROBABILITY) {
+        for (int i = 0; i < sampledInputSize; ++i) {
+            std::stringstream sstream;
+            sstream << i << ", ";
+            sstream << "(" << (*sampledInputXs)[i] << ", " << (*sampledInputYs)[i] << "), ";
+            sstream << "Speed: "<< (*sampledSpeedRates)[i] << ", ";
+            sstream << "Angle: "<< getPointAngle(sampledInputXs, sampledInputYs, i) << ", \n";
+
+            for (hash_map_compat<int, float>::iterator it = (*charProbabilities)[i].begin();
+                    it != (*charProbabilities)[i].end(); ++it) {
+                if (it->first == NOT_AN_INDEX) {
+                    sstream << it->first
+                            << "(skip):"
+                            << it->second
+                            << "\n";
+                } else {
+                    sstream << it->first
+                            << "("
+                            //<< static_cast<char>(mProximityInfo->getCodePointOf(it->first))
+                            << "):"
+                            << it->second
+                            << "\n";
+                }
+            }
+            AKLOGI("%s", sstream.str().c_str());
+        }
+    }
+
+    // Decrease key probabilities of points which don't have the highest probability of that key
+    // among nearby points. Probabilities of the first point and the last point are not suppressed.
+    for (int i = max(start, 1); i < sampledInputSize; ++i) {
+        for (int j = i + 1; j < sampledInputSize; ++j) {
+            if (!suppressCharProbabilities(
+                    mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
+                    charProbabilities)) {
+                break;
+            }
+        }
+        for (int j = i - 1; j >= max(start, 0); --j) {
+            if (!suppressCharProbabilities(
+                    mostCommonKeyWidth, sampledInputSize, sampledLengthCache, i, j,
+                    charProbabilities)) {
+                break;
+            }
+        }
+    }
+
+    // Converting from raw probabilities to log probabilities to calculate spatial distance.
+    for (int i = start; i < sampledInputSize; ++i) {
+        for (int j = 0; j < keyCount; ++j) {
+            hash_map_compat<int, float>::iterator it = (*charProbabilities)[i].find(j);
+            if (it == (*charProbabilities)[i].end()){
+                (*sampledNearKeySets)[i].reset(j);
+            } else if(it->second < ProximityInfoParams::MIN_PROBABILITY) {
+                // Erases from near keys vector because it has very low probability.
+                (*sampledNearKeySets)[i].reset(j);
+                (*charProbabilities)[i].erase(j);
+            } else {
+                it->second = -logf(it->second);
+            }
+        }
+        (*charProbabilities)[i][NOT_AN_INDEX] = -logf((*charProbabilities)[i][NOT_AN_INDEX]);
+    }
+}
+
+/* static */ void ProximityInfoStateUtils::updateSampledSearchKeySets(
+        const ProximityInfo *const proximityInfo, const int sampledInputSize,
+        const int lastSavedInputSize,
+        const std::vector<int> *const sampledLengthCache,
+        const std::vector<NearKeycodesSet> *const sampledNearKeySets,
+        std::vector<NearKeycodesSet> *sampledSearchKeySets,
+        std::vector<std::vector<int> > *sampledSearchKeyVectors) {
+    sampledSearchKeySets->resize(sampledInputSize);
+    sampledSearchKeyVectors->resize(sampledInputSize);
+    const int readForwordLength = static_cast<int>(
+            hypotf(proximityInfo->getKeyboardWidth(), proximityInfo->getKeyboardHeight())
+                    * ProximityInfoParams::SEARCH_KEY_RADIUS_RATIO);
+    for (int i = 0; i < sampledInputSize; ++i) {
+        if (i >= lastSavedInputSize) {
+            (*sampledSearchKeySets)[i].reset();
+        }
+        for (int j = max(i, lastSavedInputSize); j < sampledInputSize; ++j) {
+            // TODO: Investigate if this is required. This may not fail.
+            if ((*sampledLengthCache)[j] - (*sampledLengthCache)[i] >= readForwordLength) {
+                break;
+            }
+            (*sampledSearchKeySets)[i] |= (*sampledNearKeySets)[j];
+        }
+    }
+    const int keyCount = proximityInfo->getKeyCount();
+    for (int i = 0; i < sampledInputSize; ++i) {
+        std::vector<int> *searchKeyVector = &(*sampledSearchKeyVectors)[i];
+        searchKeyVector->clear();
+        for (int j = 0; j < keyCount; ++j) {
+            if ((*sampledSearchKeySets)[i].test(j)) {
+                const int keyCodePoint = proximityInfo->getCodePointOf(j);
+                if (std::find(searchKeyVector->begin(), searchKeyVector->end(), keyCodePoint)
+                        == searchKeyVector->end()) {
+                    searchKeyVector->push_back(keyCodePoint);
+                }
+            }
+        }
+    }
+}
+
+// Decreases char probabilities of index0 by checking probabilities of a near point (index1) and
+// increases char probabilities of index1 by checking probabilities of index0.
+/* static */ bool ProximityInfoStateUtils::suppressCharProbabilities(const int mostCommonKeyWidth,
+        const int sampledInputSize, const std::vector<int> *const lengthCache,
+        const int index0, const int index1,
+        std::vector<hash_map_compat<int, float> > *charProbabilities) {
+    ASSERT(0 <= index0 && index0 < sampledInputSize);
+    ASSERT(0 <= index1 && index1 < sampledInputSize);
+    const float keyWidthFloat = static_cast<float>(mostCommonKeyWidth);
+    const float diff = fabsf(static_cast<float>((*lengthCache)[index0] - (*lengthCache)[index1]));
+    if (diff > keyWidthFloat * ProximityInfoParams::SUPPRESSION_LENGTH_WEIGHT) {
+        return false;
+    }
+    const float suppressionRate = ProximityInfoParams::MIN_SUPPRESSION_RATE
+            + diff / keyWidthFloat / ProximityInfoParams::SUPPRESSION_LENGTH_WEIGHT
+                    * ProximityInfoParams::SUPPRESSION_WEIGHT;
+    for (hash_map_compat<int, float>::iterator it = (*charProbabilities)[index0].begin();
+            it != (*charProbabilities)[index0].end(); ++it) {
+        hash_map_compat<int, float>::iterator it2 =  (*charProbabilities)[index1].find(it->first);
+        if (it2 != (*charProbabilities)[index1].end() && it->second < it2->second) {
+            const float newProbability = it->second * suppressionRate;
+            const float suppression = it->second - newProbability;
+            it->second = newProbability;
+            // mCharProbabilities[index0][NOT_AN_INDEX] is the probability of skipping this point.
+            (*charProbabilities)[index0][NOT_AN_INDEX] += suppression;
+
+            // Add the probability of the same key nearby index1
+            const float probabilityGain = min(suppression
+                    * ProximityInfoParams::SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN,
+                    (*charProbabilities)[index1][NOT_AN_INDEX]
+                            * ProximityInfoParams::SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN);
+            it2->second += probabilityGain;
+            (*charProbabilities)[index1][NOT_AN_INDEX] -= probabilityGain;
+        }
+    }
+    return true;
+}
+
+/* static */ bool ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
+        const int inputSize, const int *const xCoordinates, const int *const yCoordinates,
+        const int *const times, const int sampledInputSize,
+        const std::vector<int> *const sampledInputXs, const std::vector<int> *const sampledInputYs,
+        const std::vector<int> *const sampledTimes,
+        const std::vector<int> *const sampledInputIndices) {
+    if (inputSize < sampledInputSize) {
+        return false;
+    }
+    for (int i = 0; i < sampledInputSize; ++i) {
+        const int index = (*sampledInputIndices)[i];
+        if (index >= inputSize) {
+            return false;
+        }
+        if (xCoordinates[index] != (*sampledInputXs)[i]
+                || yCoordinates[index] != (*sampledInputYs)[i]) {
+            return false;
+        }
+        if (!times) {
+            continue;
+        }
+        if (times[index] != (*sampledTimes)[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// Get a word that is detected by tracing the most probable string into codePointBuf and
+// returns probability of generating the word.
+/* static */ float ProximityInfoStateUtils::getMostProbableString(
+        const ProximityInfo *const proximityInfo, const int sampledInputSize,
+        const std::vector<hash_map_compat<int, float> > *const charProbabilities,
+        int *const codePointBuf) {
+    ASSERT(sampledInputSize >= 0);
+    memset(codePointBuf, 0, sizeof(codePointBuf[0]) * MAX_WORD_LENGTH);
+    int index = 0;
+    float sumLogProbability = 0.0f;
+    // TODO: Current implementation is greedy algorithm. DP would be efficient for many cases.
+    for (int i = 0; i < sampledInputSize && index < MAX_WORD_LENGTH - 1; ++i) {
+        float minLogProbability = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+        int character = NOT_AN_INDEX;
+        for (hash_map_compat<int, float>::const_iterator it = (*charProbabilities)[i].begin();
+                it != (*charProbabilities)[i].end(); ++it) {
+            const float logProbability = (it->first != NOT_AN_INDEX)
+                    ? it->second + ProximityInfoParams::DEMOTION_LOG_PROBABILITY : it->second;
+            if (logProbability < minLogProbability) {
+                minLogProbability = logProbability;
+                character = it->first;
+            }
+        }
+        if (character != NOT_AN_INDEX) {
+            codePointBuf[index] = proximityInfo->getCodePointOf(character);
+            index++;
+        }
+        sumLogProbability += minLogProbability;
+    }
+    codePointBuf[index] = '\0';
+    return sumLogProbability;
+}
+
+/* static */ void ProximityInfoStateUtils::dump(const bool isGeometric, const int inputSize,
+        const int *const inputXCoordinates, const int *const inputYCoordinates,
+        const int sampledInputSize, const std::vector<int> *const sampledInputXs,
+        const std::vector<int> *const sampledInputYs,
+        const std::vector<int> *const sampledTimes,
+        const std::vector<float> *const sampledSpeedRates,
+        const std::vector<int> *const sampledBeelineSpeedPercentiles) {
+    if (DEBUG_GEO_FULL) {
+        for (int i = 0; i < sampledInputSize; ++i) {
+            AKLOGI("Sampled(%d): x = %d, y = %d, time = %d", i, (*sampledInputXs)[i],
+                    (*sampledInputYs)[i], sampledTimes ? (*sampledTimes)[i] : -1);
+        }
+    }
+
+    std::stringstream originalX, originalY, sampledX, sampledY;
+    for (int i = 0; i < inputSize; ++i) {
+        originalX << inputXCoordinates[i];
+        originalY << inputYCoordinates[i];
+        if (i != inputSize - 1) {
+            originalX << ";";
+            originalY << ";";
+        }
+    }
+    AKLOGI("===== sampled points =====");
+    for (int i = 0; i < sampledInputSize; ++i) {
+        if (isGeometric) {
+            AKLOGI("%d: x = %d, y = %d, time = %d, relative speed = %.4f, beeline speed = %d",
+                    i, (*sampledInputXs)[i], (*sampledInputYs)[i], (*sampledTimes)[i],
+                    (*sampledSpeedRates)[i], (*sampledBeelineSpeedPercentiles)[i]);
+        }
+        sampledX << (*sampledInputXs)[i];
+        sampledY << (*sampledInputYs)[i];
+        if (i != sampledInputSize - 1) {
+            sampledX << ";";
+            sampledY << ";";
+        }
+    }
+    AKLOGI("original points:\n%s, %s,\nsampled points:\n%s, %s,\n",
+            originalX.str().c_str(), originalY.str().c_str(), sampledX.str().c_str(),
+            sampledY.str().c_str());
+}
+} // namespace latinime
diff --git a/src/aosp/suggest/core/layout/proximity_info_state_utils.h b/src/aosp/suggest/core/layout/proximity_info_state_utils.h
new file mode 100644
index 0000000..6de9700
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info_state_utils.h
@@ -0,0 +1,165 @@
+/*
+ * 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_PROXIMITY_INFO_STATE_UTILS_H
+#define LATINIME_PROXIMITY_INFO_STATE_UTILS_H
+
+#include <bitset>
+#include <vector>
+
+#include "defines.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+class ProximityInfo;
+class ProximityInfoParams;
+
+class ProximityInfoStateUtils {
+ public:
+    typedef hash_map_compat<int, float> NearKeysDistanceMap;
+    typedef std::bitset<MAX_KEY_COUNT_IN_A_KEYBOARD> NearKeycodesSet;
+
+    static int trimLastTwoTouchPoints(std::vector<int> *sampledInputXs,
+            std::vector<int> *sampledInputYs, std::vector<int> *sampledInputTimes,
+            std::vector<int> *sampledLengthCache, std::vector<int> *sampledInputIndice);
+    static int updateTouchPoints(const ProximityInfo *const proximityInfo,
+            const int maxPointToKeyLength, const int *const inputProximities,
+            const int *const inputXCoordinates, const int *const inputYCoordinates,
+            const int *const times, const int *const pointerIds, const int inputSize,
+            const bool isGeometric, const int pointerId, const int pushTouchPointStartIndex,
+            std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
+            std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
+            std::vector<int> *sampledInputIndice);
+    static const int *getProximityCodePointsAt(const int *const inputProximities, const int index);
+    static int getPrimaryCodePointAt(const int *const inputProximities, const int index);
+    static void popInputData(std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
+            std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
+            std::vector<int> *sampledInputIndice);
+    static float refreshSpeedRates(const int inputSize, const int *const xCoordinates,
+            const int *const yCoordinates, const int *const times, const int lastSavedInputSize,
+            const int sampledInputSize, const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs,
+            const std::vector<int> *const sampledInputTimes,
+            const std::vector<int> *const sampledLengthCache,
+            const std::vector<int> *const sampledInputIndice,
+            std::vector<float> *sampledSpeedRates, std::vector<float> *sampledDirections);
+    static void refreshBeelineSpeedRates(const int mostCommonKeyWidth,  const float averageSpeed,
+            const int inputSize, const int *const xCoordinates, const int *const yCoordinates,
+            const int *times, const int sampledInputSize,
+            const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs, const std::vector<int> *const inputIndice,
+            std::vector<int> *beelineSpeedPercentiles);
+    static float getDirection(const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs, const int index0, const int index1);
+    static void updateAlignPointProbabilities(const float maxPointToKeyLength,
+            const int mostCommonKeyWidth, const int keyCount, const int start,
+            const int sampledInputSize, const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs,
+            const std::vector<float> *const sampledSpeedRates,
+            const std::vector<int> *const sampledLengthCache,
+            const std::vector<float> *const sampledNormalizedSquaredLengthCache,
+            std::vector<NearKeycodesSet> *sampledNearKeySets,
+            std::vector<hash_map_compat<int, float> > *charProbabilities);
+    static void updateSampledSearchKeySets(const ProximityInfo *const proximityInfo,
+            const int sampledInputSize, const int lastSavedInputSize,
+            const std::vector<int> *const sampledLengthCache,
+            const std::vector<NearKeycodesSet> *const sampledNearKeySets,
+            std::vector<NearKeycodesSet> *sampledSearchKeySets,
+            std::vector<std::vector<int> > *sampledSearchKeyVectors);
+    static float getPointToKeyByIdLength(const float maxPointToKeyLength,
+            const std::vector<float> *const sampledNormalizedSquaredLengthCache, const int keyCount,
+            const int inputIndex, const int keyId);
+    static void initGeometricDistanceInfos(const ProximityInfo *const proximityInfo,
+            const int sampledInputSize, const int lastSavedInputSize, const bool isGeometric,
+            const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs,
+            std::vector<NearKeycodesSet> *sampledNearKeySets,
+            std::vector<float> *sampledNormalizedSquaredLengthCache);
+    static void initPrimaryInputWord(const int inputSize, const int *const inputProximities,
+            int *primaryInputWord);
+    static void initNormalizedSquaredDistances(const ProximityInfo *const proximityInfo,
+            const int inputSize, const int *inputXCoordinates, const int *inputYCoordinates,
+            const int *const inputProximities, const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs, int *normalizedSquaredDistances);
+    static void dump(const bool isGeometric, const int inputSize,
+            const int *const inputXCoordinates, const int *const inputYCoordinates,
+            const int sampledInputSize, const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs,
+            const std::vector<int> *const sampledTimes,
+            const std::vector<float> *const sampledSpeedRates,
+            const std::vector<int> *const sampledBeelineSpeedPercentiles);
+    static bool checkAndReturnIsContinuousSuggestionPossible(const int inputSize,
+            const int *const xCoordinates, const int *const yCoordinates, const int *const times,
+            const int sampledInputSize, const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs,
+            const std::vector<int> *const sampledTimes,
+            const std::vector<int> *const sampledInputIndices);
+    // TODO: Move to most_probable_string_utils.h
+    static float getMostProbableString(const ProximityInfo *const proximityInfo,
+            const int sampledInputSize,
+            const std::vector<hash_map_compat<int, float> > *const charProbabilities,
+            int *const codePointBuf);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfoStateUtils);
+
+    static float updateNearKeysDistances(const ProximityInfo *const proximityInfo,
+            const float maxPointToKeyLength, const int x, const int y,
+            const bool isGeometric,
+            NearKeysDistanceMap *const currentNearKeysDistances);
+    static bool isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
+            const NearKeysDistanceMap *const prevNearKeysDistances,
+            const NearKeysDistanceMap *const prevPrevNearKeysDistances);
+    static float getPointScore(const int mostCommonKeyWidth, const int x, const int y,
+            const int time, const bool lastPoint, const float nearest, const float sumAngle,
+            const NearKeysDistanceMap *const currentNearKeysDistances,
+            const NearKeysDistanceMap *const prevNearKeysDistances,
+            const NearKeysDistanceMap *const prevPrevNearKeysDistances,
+            std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs);
+    static bool pushTouchPoint(const ProximityInfo *const proximityInfo,
+            const int maxPointToKeyLength, const int inputIndex, const int nodeCodePoint, int x,
+            int y, const int time, const bool isGeometric,
+            const bool doSampling, const bool isLastPoint,
+            const float sumAngle, NearKeysDistanceMap *const currentNearKeysDistances,
+            const NearKeysDistanceMap *const prevNearKeysDistances,
+            const NearKeysDistanceMap *const prevPrevNearKeysDistances,
+            std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
+            std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
+            std::vector<int> *sampledInputIndice);
+    static float calculateBeelineSpeedRate(const int mostCommonKeyWidth, const float averageSpeed,
+            const int id, const int inputSize, const int *const xCoordinates,
+            const int *const yCoordinates, const int *times, const int sampledInputSize,
+            const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs,
+            const std::vector<int> *const inputIndice);
+    static float getPointAngle(const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs, const int index);
+    static float getPointsAngle(const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs, const int index0, const int index1,
+            const int index2);
+    static bool suppressCharProbabilities(const int mostCommonKeyWidth,
+            const int sampledInputSize, const std::vector<int> *const lengthCache, const int index0,
+            const int index1, std::vector<hash_map_compat<int, float> > *charProbabilities);
+    static float calculateSquaredDistanceFromSweetSpotCenter(
+            const ProximityInfo *const proximityInfo, const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs, const int keyIndex,
+            const int inputIndex);
+     static float calculateNormalizedSquaredDistance(const ProximityInfo *const proximityInfo,
+            const std::vector<int> *const sampledInputXs,
+            const std::vector<int> *const sampledInputYs, const int keyIndex, const int inputIndex);
+};
+} // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_STATE_UTILS_H
diff --git a/src/aosp/suggest/core/layout/proximity_info_utils.h b/src/aosp/suggest/core/layout/proximity_info_utils.h
new file mode 100644
index 0000000..0e28560
--- /dev/null
+++ b/src/aosp/suggest/core/layout/proximity_info_utils.h
@@ -0,0 +1,249 @@
+/*
+ * 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_PROXIMITY_INFO_UTILS_H
+#define LATINIME_PROXIMITY_INFO_UTILS_H
+
+#include <cmath>
+
+#include "defines.h"
+#include "suggest/core/layout/additional_proximity_chars.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "utils/char_utils.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+class ProximityInfoUtils {
+ public:
+    static AK_FORCE_INLINE int getKeyIndexOf(const int keyCount, const int c,
+            const hash_map_compat<int, int> *const codeToKeyMap) {
+        if (keyCount == 0) {
+            // We do not have the coordinate data
+            return NOT_AN_INDEX;
+        }
+        if (c == NOT_A_CODE_POINT) {
+            return NOT_AN_INDEX;
+        }
+        const int lowerCode = CharUtils::toLowerCase(c);
+        hash_map_compat<int, int>::const_iterator mapPos = codeToKeyMap->find(lowerCode);
+        if (mapPos != codeToKeyMap->end()) {
+            return mapPos->second;
+        }
+        return NOT_AN_INDEX;
+    }
+
+    static AK_FORCE_INLINE void initializeProximities(const int *const inputCodes,
+            const int *const inputXCoordinates, const int *const inputYCoordinates,
+            const int inputSize, const int *const keyXCoordinates,
+            const int *const keyYCoordinates, const int *const keyWidths, const int *keyHeights,
+            const int *const proximityCharsArray, const int cellHeight, const int cellWidth,
+            const int gridWidth, const int mostCommonKeyWidth, const int keyCount,
+            const char *const localeStr,
+            const hash_map_compat<int, int> *const codeToKeyMap, int *inputProximities) {
+        // Initialize
+        // - mInputCodes
+        // - mNormalizedSquaredDistances
+        // TODO: Merge
+        for (int i = 0; i < inputSize; ++i) {
+            const int primaryKey = inputCodes[i];
+            const int x = inputXCoordinates[i];
+            const int y = inputYCoordinates[i];
+            int *proximities = &inputProximities[i * MAX_PROXIMITY_CHARS_SIZE];
+            calculateProximities(keyXCoordinates, keyYCoordinates, keyWidths, keyHeights,
+                    proximityCharsArray, cellHeight, cellWidth, gridWidth, mostCommonKeyWidth,
+                    keyCount, x, y, primaryKey, localeStr, codeToKeyMap, proximities);
+        }
+
+        if (DEBUG_PROXIMITY_CHARS) {
+            for (int i = 0; i < inputSize; ++i) {
+                AKLOGI("---");
+                for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE; ++j) {
+                    int proximityChar =
+                            inputProximities[i * MAX_PROXIMITY_CHARS_SIZE + j];
+                    proximityChar += 0;
+                    AKLOGI("--- (%d)%c", i, proximityChar);
+                }
+            }
+        }
+    }
+
+    static AK_FORCE_INLINE int getStartIndexFromCoordinates(const int x, const int y,
+            const int cellHeight, const int cellWidth, const int gridWidth) {
+        return ((y / cellHeight) * gridWidth + (x / cellWidth)) * MAX_PROXIMITY_CHARS_SIZE;
+    }
+
+    static inline float getSquaredDistanceFloat(const float x1, const float y1, const float x2,
+            const float y2) {
+        return GeometryUtils::SQUARE_FLOAT(x1 - x2) + GeometryUtils::SQUARE_FLOAT(y1 - y2);
+    }
+
+    static inline float pointToLineSegSquaredDistanceFloat(const float x, const float y,
+        const float x1, const float y1, const float x2, const float y2, const bool extend) {
+        const float ray1x = x - x1;
+        const float ray1y = y - y1;
+        const float ray2x = x2 - x1;
+        const float ray2y = y2 - y1;
+
+        const float dotProduct = ray1x * ray2x + ray1y * ray2y;
+        const float lineLengthSqr = GeometryUtils::SQUARE_FLOAT(ray2x)
+                + GeometryUtils::SQUARE_FLOAT(ray2y);
+        const float projectionLengthSqr = dotProduct / lineLengthSqr;
+
+        float projectionX;
+        float projectionY;
+        if (!extend && projectionLengthSqr < 0.0f) {
+            projectionX = x1;
+            projectionY = y1;
+        } else if (!extend && projectionLengthSqr > 1.0f) {
+            projectionX = x2;
+            projectionY = y2;
+        } else {
+            projectionX = x1 + projectionLengthSqr * ray2x;
+            projectionY = y1 + projectionLengthSqr * ray2y;
+        }
+        return getSquaredDistanceFloat(x, y, projectionX, projectionY);
+    }
+
+     static AK_FORCE_INLINE bool isMatchOrProximityChar(const ProximityType type) {
+         return type == MATCH_CHAR || type == PROXIMITY_CHAR || type == ADDITIONAL_PROXIMITY_CHAR;
+     }
+
+    // Normal distribution N(u, sigma^2).
+    struct NormalDistribution {
+     public:
+        NormalDistribution(const float u, const float sigma)
+                : mU(u), mSigma(sigma),
+                  mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F
+                          * GeometryUtils::SQUARE_FLOAT(sigma))),
+                  mPreComputedExponentPart(-1.0f / (2.0f * GeometryUtils::SQUARE_FLOAT(sigma))) {}
+
+        float getProbabilityDensity(const float x) const {
+            const float shiftedX = x - mU;
+            return mPreComputedNonExpPart
+                    * expf(mPreComputedExponentPart * GeometryUtils::SQUARE_FLOAT(shiftedX));
+        }
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(NormalDistribution);
+        const float mU; // mean value
+        const float mSigma; // standard deviation
+        const float mPreComputedNonExpPart; // = 1 / sqrt(2 * PI * sigma^2)
+        const float mPreComputedExponentPart; // = -1 / (2 * sigma^2)
+    }; // struct NormalDistribution
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfoUtils);
+
+    static bool isOnKey(const int *const keyXCoordinates, const int *const keyYCoordinates,
+            const int *const keyWidths, const int *keyHeights, const int keyId, const int x,
+            const int y) {
+        if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
+        const int left = keyXCoordinates[keyId];
+        const int top = keyYCoordinates[keyId];
+        const int right = left + keyWidths[keyId] + 1;
+        const int bottom = top + keyHeights[keyId];
+        return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
+    }
+
+    static AK_FORCE_INLINE void calculateProximities(const int *const keyXCoordinates,
+            const int *const keyYCoordinates, const int *const keyWidths, const int *keyHeights,
+            const int *const proximityCharsArray, const int cellHeight, const int cellWidth,
+            const int gridWidth, const int mostCommonKeyWidth, const int keyCount,
+            const int x, const int y, const int primaryKey, const char *const localeStr,
+            const hash_map_compat<int, int> *const codeToKeyMap, int *proximities) {
+        const int mostCommonKeyWidthSquare = mostCommonKeyWidth * mostCommonKeyWidth;
+        int insertPos = 0;
+        proximities[insertPos++] = primaryKey;
+        const int startIndex = getStartIndexFromCoordinates(x, y, cellHeight, cellWidth, gridWidth);
+        if (startIndex >= 0) {
+            for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+                const int c = proximityCharsArray[startIndex + i];
+                if (c < KEYCODE_SPACE || c == primaryKey) {
+                    continue;
+                }
+                const int keyIndex = getKeyIndexOf(keyCount, c, codeToKeyMap);
+                const bool onKey = isOnKey(keyXCoordinates, keyYCoordinates, keyWidths, keyHeights,
+                        keyIndex, x, y);
+                const int distance = squaredLengthToEdge(keyXCoordinates, keyYCoordinates,
+                        keyWidths, keyHeights, keyIndex, x, y);
+                if (onKey || distance < mostCommonKeyWidthSquare) {
+                    proximities[insertPos++] = c;
+                    if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+                        if (DEBUG_DICT) {
+                            ASSERT(false);
+                        }
+                        return;
+                    }
+                }
+            }
+            const int additionalProximitySize =
+                    AdditionalProximityChars::getAdditionalCharsSize(localeStr, primaryKey);
+            if (additionalProximitySize > 0) {
+                proximities[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
+                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+                    if (DEBUG_DICT) {
+                        ASSERT(false);
+                    }
+                    return;
+                }
+
+                const int *additionalProximityChars =
+                        AdditionalProximityChars::getAdditionalChars(localeStr, primaryKey);
+                for (int j = 0; j < additionalProximitySize; ++j) {
+                    const int ac = additionalProximityChars[j];
+                    int k = 0;
+                    for (; k < insertPos; ++k) {
+                        if (ac == proximities[k]) {
+                            break;
+                        }
+                    }
+                    if (k < insertPos) {
+                        continue;
+                    }
+                    proximities[insertPos++] = ac;
+                    if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+                        if (DEBUG_DICT) {
+                            ASSERT(false);
+                        }
+                        return;
+                    }
+                }
+            }
+        }
+        // Add a delimiter for the proximity characters
+        for (int i = insertPos; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+            proximities[i] = NOT_A_CODE_POINT;
+        }
+    }
+
+    static int squaredLengthToEdge(const int *const keyXCoordinates,
+            const int *const keyYCoordinates, const int *const keyWidths, const int *keyHeights,
+            const int keyId, const int x, const int y) {
+        // NOT_A_ID is -1, but return whenever < 0 just in case
+        if (keyId < 0) return MAX_VALUE_FOR_WEIGHTING;
+        const int left = keyXCoordinates[keyId];
+        const int top = keyYCoordinates[keyId];
+        const int right = left + keyWidths[keyId];
+        const int bottom = top + keyHeights[keyId];
+        const int edgeX = x < left ? left : (x > right ? right : x);
+        const int edgeY = y < top ? top : (y > bottom ? bottom : y);
+        const int dx = x - edgeX;
+        const int dy = y - edgeY;
+        return dx * dx + dy * dy;
+    }
+};
+} // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_UTILS_H
diff --git a/src/aosp/suggest/core/layout/touch_position_correction_utils.h b/src/aosp/suggest/core/layout/touch_position_correction_utils.h
new file mode 100644
index 0000000..9130e87
--- /dev/null
+++ b/src/aosp/suggest/core/layout/touch_position_correction_utils.h
@@ -0,0 +1,63 @@
+/*
+ * 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_TOUCH_POSITION_CORRECTION_UTILS_H
+#define LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
+
+#include "defines.h"
+#include "suggest/core/layout/proximity_info_params.h"
+
+namespace latinime {
+class TouchPositionCorrectionUtils {
+ public:
+    static float getSweetSpotFactor(const bool isTouchPositionCorrectionEnabled,
+            const float normalizedSquaredDistance) {
+        // Promote or demote the score according to the distance from the sweet spot
+        static const float A = 0.0f;
+        static const float B = 0.24f;
+        static const float C = 1.20f;
+        static const float R0 = 0.0f;
+        static const float R1 = 0.25f; // Sweet spot
+        static const float R2 = 1.0f;
+        const float x = normalizedSquaredDistance;
+        if (!isTouchPositionCorrectionEnabled) {
+            return min(C, x);
+        }
+
+        // factor is a piecewise linear function like:
+        // C        -------------.
+        //         /             .
+        // B      /              .
+        //      -/               .
+        // A _-^                 .
+        //                       .
+        //   R0 R1 R2            .
+
+        if (x < R0) {
+            return A;
+        } else if (x < R1) {
+            return (A * (R1 - x) + B * (x - R0)) / (R1 - R0);
+        } else if (x < R2) {
+            return (B * (R2 - x) + C * (x - R1)) / (R2 - R1);
+        } else {
+            return C;
+        }
+    }
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(TouchPositionCorrectionUtils);
+};
+} // namespace latinime
+#endif // LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
diff --git a/src/aosp/suggest/core/policy/dictionary_bigrams_structure_policy.h b/src/aosp/suggest/core/policy/dictionary_bigrams_structure_policy.h
new file mode 100644
index 0000000..661ef1b
--- /dev/null
+++ b/src/aosp/suggest/core/policy/dictionary_bigrams_structure_policy.h
@@ -0,0 +1,42 @@
+/*
+ * 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_DICTIONARY_BIGRAMS_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_BIGRAMS_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+/*
+ * This class abstracts structure of bigrams.
+ */
+class DictionaryBigramsStructurePolicy {
+ public:
+    virtual ~DictionaryBigramsStructurePolicy() {}
+
+    virtual void getNextBigram(int *const outBigramPos, int *const outProbability,
+            bool *const outHasNext, int *const pos) const = 0;
+    virtual void skipAllBigrams(int *const pos) const = 0;
+
+ protected:
+    DictionaryBigramsStructurePolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryBigramsStructurePolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_BIGRAMS_STRUCTURE_POLICY_H */
diff --git a/src/aosp/suggest/core/policy/dictionary_header_structure_policy.h b/src/aosp/suggest/core/policy/dictionary_header_structure_policy.h
new file mode 100644
index 0000000..5492c60
--- /dev/null
+++ b/src/aosp/suggest/core/policy/dictionary_header_structure_policy.h
@@ -0,0 +1,52 @@
+/*
+ * 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_DICTIONARY_HEADER_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_HEADER_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+/*
+ * This class abstracts structure of dictionaries.
+ * Implement this policy to support additional dictionaries.
+ */
+class DictionaryHeaderStructurePolicy {
+ public:
+    virtual ~DictionaryHeaderStructurePolicy() {}
+
+    virtual bool supportsDynamicUpdate() const = 0;
+
+    virtual bool requiresGermanUmlautProcessing() const = 0;
+
+    virtual bool requiresFrenchLigatureProcessing() const = 0;
+
+    virtual float getMultiWordCostMultiplier() const = 0;
+
+    virtual int getLastDecayedTime() const = 0;
+
+    virtual void readHeaderValueOrQuestionMark(const char *const key, int *outValue,
+            int outValueSize) const = 0;
+
+ protected:
+    DictionaryHeaderStructurePolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryHeaderStructurePolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_HEADER_STRUCTURE_POLICY_H */
diff --git a/src/aosp/suggest/core/policy/dictionary_shortcuts_structure_policy.h b/src/aosp/suggest/core/policy/dictionary_shortcuts_structure_policy.h
new file mode 100644
index 0000000..40b6c2d
--- /dev/null
+++ b/src/aosp/suggest/core/policy/dictionary_shortcuts_structure_policy.h
@@ -0,0 +1,46 @@
+/*
+ * 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_DICTIONARY_SHORTCUTS_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_SHORTCUTS_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+/*
+ * This class abstracts structure of shortcuts.
+ */
+class DictionaryShortcutsStructurePolicy {
+ public:
+    virtual ~DictionaryShortcutsStructurePolicy() {}
+
+    virtual int getStartPos(const int pos) const = 0;
+
+    virtual void getNextShortcut(const int maxCodePointCount, int *const outCodePoint,
+            int *const outCodePointCount, bool *const outIsWhitelist, bool *const outHasNext,
+            int *const pos) const = 0;
+
+    virtual void skipAllShortcuts(int *const pos) const = 0;
+
+ protected:
+    DictionaryShortcutsStructurePolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryShortcutsStructurePolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_SHORTCUTS_STRUCTURE_POLICY_H */
diff --git a/src/aosp/suggest/core/policy/dictionary_structure_with_buffer_policy.h b/src/aosp/suggest/core/policy/dictionary_structure_with_buffer_policy.h
new file mode 100644
index 0000000..41f8204
--- /dev/null
+++ b/src/aosp/suggest/core/policy/dictionary_structure_with_buffer_policy.h
@@ -0,0 +1,95 @@
+/*
+ * 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_DICTIONARY_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicNode;
+class DicNodeVector;
+class DictionaryBigramsStructurePolicy;
+class DictionaryHeaderStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This class abstracts structure of dictionaries.
+ * Implement this policy to support additional dictionaries.
+ */
+class DictionaryStructureWithBufferPolicy {
+ public:
+    virtual ~DictionaryStructureWithBufferPolicy() {}
+
+    virtual int getRootPosition() const = 0;
+
+    virtual void createAndGetAllChildNodes(const DicNode *const dicNode,
+            DicNodeVector *const childDicNodes) const = 0;
+
+    virtual int getCodePointsAndProbabilityAndReturnCodePointCount(
+            const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+            int *const outUnigramProbability) const = 0;
+
+    virtual int getTerminalNodePositionOfWord(const int *const inWord,
+            const int length, const bool forceLowerCaseSearch) const = 0;
+
+    virtual int getProbability(const int unigramProbability,
+            const int bigramProbability) const = 0;
+
+    virtual int getUnigramProbabilityOfPtNode(const int nodePos) const = 0;
+
+    virtual int getShortcutPositionOfPtNode(const int nodePos) const = 0;
+
+    virtual int getBigramsPositionOfPtNode(const int nodePos) const = 0;
+
+    virtual const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const = 0;
+
+    virtual const DictionaryBigramsStructurePolicy *getBigramsStructurePolicy() const = 0;
+
+    virtual const DictionaryShortcutsStructurePolicy *getShortcutsStructurePolicy() const = 0;
+
+    // Returns whether the update was success or not.
+    virtual bool addUnigramWord(const int *const word, const int length,
+            const int probability) = 0;
+
+    // Returns whether the update was success or not.
+    virtual bool addBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1, const int probability) = 0;
+
+    // Returns whether the update was success or not.
+    virtual bool removeBigramWords(const int *const word0, const int length0,
+            const int *const word1, const int length1) = 0;
+
+    virtual void flush(const char *const filePath) = 0;
+
+    virtual void flushWithGC(const char *const filePath) = 0;
+
+    virtual bool needsToRunGC(const bool mindsBlockByGC) const = 0;
+
+    // Currently, this method is used only for testing. You may want to consider creating new
+    // dedicated method instead of this if you want to use this in the production.
+    virtual void getProperty(const char *const query, char *const outResult,
+            const int maxResultLength) = 0;
+
+ protected:
+    DictionaryStructureWithBufferPolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryStructureWithBufferPolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_STRUCTURE_POLICY_H */
diff --git a/src/aosp/suggest/core/policy/scoring.h b/src/aosp/suggest/core/policy/scoring.h
new file mode 100644
index 0000000..102e856
--- /dev/null
+++ b/src/aosp/suggest/core/policy/scoring.h
@@ -0,0 +1,55 @@
+/*
+ * 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_SCORING_H
+#define LATINIME_SCORING_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicNode;
+class DicTraverseSession;
+
+// This class basically tweaks suggestions and distances apart from CompoundDistance
+class Scoring {
+ public:
+    virtual int calculateFinalScore(const float compoundDistance, const int inputSize,
+            const bool forceCommit) const = 0;
+    virtual bool getMostProbableString(const DicTraverseSession *const traverseSession,
+            const int terminalSize, const float languageWeight, int *const outputCodePoints,
+            int *const type, int *const freq) const = 0;
+    virtual void safetyNetForMostProbableString(const int terminalSize,
+            const int maxScore, int *const outputCodePoints, int *const frequencies) const = 0;
+    // TODO: Make more generic
+    virtual void searchWordWithDoubleLetter(DicNode *terminals, const int terminalSize,
+            int *doubleLetterTerminalIndex, DoubleLetterLevel *doubleLetterLevel) const = 0;
+    virtual float getAdjustedLanguageWeight(DicTraverseSession *const traverseSession,
+            DicNode *const terminals, const int size) const = 0;
+    virtual float getDoubleLetterDemotionDistanceCost(const int terminalIndex,
+            const int doubleLetterTerminalIndex,
+            const DoubleLetterLevel doubleLetterLevel) const = 0;
+    virtual bool doesAutoCorrectValidWord() const = 0;
+
+ protected:
+    Scoring() {}
+    virtual ~Scoring() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(Scoring);
+};
+} // namespace latinime
+#endif // LATINIME_SCORING_H
diff --git a/src/aosp/suggest/core/policy/suggest_policy.h b/src/aosp/suggest/core/policy/suggest_policy.h
new file mode 100644
index 0000000..5b6402c
--- /dev/null
+++ b/src/aosp/suggest/core/policy/suggest_policy.h
@@ -0,0 +1,40 @@
+/*
+ * 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_SUGGEST_POLICY_H
+#define LATINIME_SUGGEST_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class Traversal;
+class Scoring;
+class Weighting;
+
+class SuggestPolicy {
+ public:
+    SuggestPolicy() {}
+    virtual ~SuggestPolicy() {}
+    virtual const Traversal *getTraversal() const = 0;
+    virtual const Scoring *getScoring() const = 0;
+    virtual const Weighting *getWeighting() const = 0;
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(SuggestPolicy);
+};
+} // namespace latinime
+#endif // LATINIME_SUGGEST_POLICY_H
diff --git a/src/aosp/suggest/core/policy/traversal.h b/src/aosp/suggest/core/policy/traversal.h
new file mode 100644
index 0000000..e935533
--- /dev/null
+++ b/src/aosp/suggest/core/policy/traversal.h
@@ -0,0 +1,63 @@
+/*
+ * 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_TRAVERSAL_H
+#define LATINIME_TRAVERSAL_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicTraverseSession;
+
+class Traversal {
+ public:
+    virtual int getMaxPointerCount() const = 0;
+    virtual bool allowsErrorCorrections(const DicNode *const dicNode) const = 0;
+    virtual bool isOmission(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode, const DicNode *const childDicNode,
+            const bool allowsErrorCorrections) const = 0;
+    virtual bool isSpaceSubstitutionTerminal(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+    virtual bool isSpaceOmissionTerminal(const DicTraverseSession *const traverseSession,
+               const DicNode *const dicNode) const = 0;
+    virtual bool shouldDepthLevelCache(const DicTraverseSession *const traverseSession) const = 0;
+    virtual bool shouldNodeLevelCache(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+    virtual bool canDoLookAheadCorrection(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+    virtual ProximityType getProximityType(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode, const DicNode *const childDicNode) const = 0;
+    virtual bool sameAsTyped(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+    virtual bool needsToTraverseAllUserInput() const = 0;
+    virtual float getMaxSpatialDistance() const = 0;
+    virtual bool autoCorrectsToMultiWordSuggestionIfTop() const = 0;
+    virtual int getDefaultExpandDicNodeSize() const = 0;
+    virtual int getMaxCacheSize(const int inputSize) const = 0;
+    virtual bool isPossibleOmissionChildNode(const DicTraverseSession *const traverseSession,
+            const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
+    virtual bool isGoodToTraverseNextWord(const DicNode *const dicNode) const = 0;
+
+ protected:
+    Traversal() {}
+    virtual ~Traversal() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(Traversal);
+};
+} // namespace latinime
+#endif // LATINIME_TRAVERSAL_H
diff --git a/src/aosp/suggest/core/policy/weighting.cpp b/src/aosp/suggest/core/policy/weighting.cpp
new file mode 100644
index 0000000..0c40168
--- /dev/null
+++ b/src/aosp/suggest/core/policy/weighting.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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/core/policy/weighting.h"
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_profiler.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/session/dic_traverse_session.h"
+
+namespace latinime {
+
+class MultiBigramMap;
+
+static inline void profile(const CorrectionType correctionType, DicNode *const node) {
+#if DEBUG_DICT
+    switch (correctionType) {
+    case CT_OMISSION:
+        PROF_OMISSION(node->mProfiler);
+        return;
+    case CT_ADDITIONAL_PROXIMITY:
+        PROF_ADDITIONAL_PROXIMITY(node->mProfiler);
+        return;
+    case CT_SUBSTITUTION:
+        PROF_SUBSTITUTION(node->mProfiler);
+        return;
+    case CT_NEW_WORD_SPACE_OMISSION:
+        PROF_NEW_WORD(node->mProfiler);
+        return;
+    case CT_MATCH:
+        PROF_MATCH(node->mProfiler);
+        return;
+    case CT_COMPLETION:
+        PROF_COMPLETION(node->mProfiler);
+        return;
+    case CT_TERMINAL:
+        PROF_TERMINAL(node->mProfiler);
+        return;
+    case CT_TERMINAL_INSERTION:
+        PROF_TERMINAL_INSERTION(node->mProfiler);
+        return;
+    case CT_NEW_WORD_SPACE_SUBSTITUTION:
+        PROF_SPACE_SUBSTITUTION(node->mProfiler);
+        return;
+    case CT_INSERTION:
+        PROF_INSERTION(node->mProfiler);
+        return;
+    case CT_TRANSPOSITION:
+        PROF_TRANSPOSITION(node->mProfiler);
+        return;
+    default:
+        // do nothing
+        return;
+    }
+#else
+    // do nothing
+#endif
+}
+
+/* static */ void Weighting::addCostAndForwardInputIndex(const Weighting *const weighting,
+        const CorrectionType correctionType, const DicTraverseSession *const traverseSession,
+        const DicNode *const parentDicNode, DicNode *const dicNode,
+        MultiBigramMap *const multiBigramMap) {
+    const int inputSize = traverseSession->getInputSize();
+    DicNode_InputStateG inputStateG;
+    inputStateG.mNeedsToUpdateInputStateG = false; // Don't use input info by default
+    const float spatialCost = Weighting::getSpatialCost(weighting, correctionType,
+            traverseSession, parentDicNode, dicNode, &inputStateG);
+    const float languageCost = Weighting::getLanguageCost(weighting, correctionType,
+            traverseSession, parentDicNode, dicNode, multiBigramMap);
+    const ErrorType errorType = weighting->getErrorType(correctionType, traverseSession,
+            parentDicNode, dicNode);
+    profile(correctionType, dicNode);
+    if (inputStateG.mNeedsToUpdateInputStateG) {
+        dicNode->updateInputIndexG(&inputStateG);
+    } else {
+        dicNode->forwardInputIndex(0, getForwardInputCount(correctionType),
+                (correctionType == CT_TRANSPOSITION));
+    }
+    dicNode->addCost(spatialCost, languageCost, weighting->needsToNormalizeCompoundDistance(),
+            inputSize, errorType);
+    if (CT_NEW_WORD_SPACE_OMISSION == correctionType) {
+        // When we are on a terminal, we save the current distance for evaluating
+        // when to auto-commit partial suggestions.
+        dicNode->saveNormalizedCompoundDistanceAfterFirstWordIfNoneYet();
+    }
+}
+
+/* static */ float Weighting::getSpatialCost(const Weighting *const weighting,
+        const CorrectionType correctionType, const DicTraverseSession *const traverseSession,
+        const DicNode *const parentDicNode, const DicNode *const dicNode,
+        DicNode_InputStateG *const inputStateG) {
+    switch(correctionType) {
+    case CT_OMISSION:
+        return weighting->getOmissionCost(parentDicNode, dicNode);
+    case CT_ADDITIONAL_PROXIMITY:
+        // only used for typing
+        return weighting->getAdditionalProximityCost();
+    case CT_SUBSTITUTION:
+        // only used for typing
+        return weighting->getSubstitutionCost();
+    case CT_NEW_WORD_SPACE_OMISSION:
+        return weighting->getNewWordSpatialCost(traverseSession, dicNode, inputStateG);
+    case CT_MATCH:
+        return weighting->getMatchedCost(traverseSession, dicNode, inputStateG);
+    case CT_COMPLETION:
+        return weighting->getCompletionCost(traverseSession, dicNode);
+    case CT_TERMINAL:
+        return weighting->getTerminalSpatialCost(traverseSession, dicNode);
+    case CT_TERMINAL_INSERTION:
+        return weighting->getTerminalInsertionCost(traverseSession, dicNode);
+    case CT_NEW_WORD_SPACE_SUBSTITUTION:
+        return weighting->getSpaceSubstitutionCost(traverseSession, dicNode);
+    case CT_INSERTION:
+        return weighting->getInsertionCost(traverseSession, parentDicNode, dicNode);
+    case CT_TRANSPOSITION:
+        return weighting->getTranspositionCost(traverseSession, parentDicNode, dicNode);
+    default:
+        return 0.0f;
+    }
+}
+
+/* static */ float Weighting::getLanguageCost(const Weighting *const weighting,
+        const CorrectionType correctionType, const DicTraverseSession *const traverseSession,
+        const DicNode *const parentDicNode, const DicNode *const dicNode,
+        MultiBigramMap *const multiBigramMap) {
+    switch(correctionType) {
+    case CT_OMISSION:
+        return 0.0f;
+    case CT_SUBSTITUTION:
+        return 0.0f;
+    case CT_NEW_WORD_SPACE_OMISSION:
+        return weighting->getNewWordBigramLanguageCost(
+                traverseSession, parentDicNode, multiBigramMap);
+    case CT_MATCH:
+        return 0.0f;
+    case CT_COMPLETION:
+        return 0.0f;
+    case CT_TERMINAL: {
+        const float languageImprobability =
+                DicNodeUtils::getBigramNodeImprobability(
+                        traverseSession->getDictionaryStructurePolicy(), dicNode, multiBigramMap);
+        return weighting->getTerminalLanguageCost(traverseSession, dicNode, languageImprobability);
+    }
+    case CT_TERMINAL_INSERTION:
+        return 0.0f;
+    case CT_NEW_WORD_SPACE_SUBSTITUTION:
+        return weighting->getNewWordBigramLanguageCost(
+                traverseSession, parentDicNode, multiBigramMap);
+    case CT_INSERTION:
+        return 0.0f;
+    case CT_TRANSPOSITION:
+        return 0.0f;
+    default:
+        return 0.0f;
+    }
+}
+
+/* static */ int Weighting::getForwardInputCount(const CorrectionType correctionType) {
+    switch(correctionType) {
+        case CT_OMISSION:
+            return 0;
+        case CT_ADDITIONAL_PROXIMITY:
+            return 0; /* 0 because CT_MATCH will be called */
+        case CT_SUBSTITUTION:
+            return 0; /* 0 because CT_MATCH will be called */
+        case CT_NEW_WORD_SPACE_OMISSION:
+            return 0;
+        case CT_MATCH:
+            return 1;
+        case CT_COMPLETION:
+            return 1;
+        case CT_TERMINAL:
+            return 0;
+        case CT_TERMINAL_INSERTION:
+            return 1;
+        case CT_NEW_WORD_SPACE_SUBSTITUTION:
+            return 1;
+        case CT_INSERTION:
+            return 2; /* look ahead + skip the current char */
+        case CT_TRANSPOSITION:
+            return 2; /* look ahead + skip the current char */
+        default:
+            return 0;
+    }
+}
+}  // namespace latinime
diff --git a/src/aosp/suggest/core/policy/weighting.h b/src/aosp/suggest/core/policy/weighting.h
new file mode 100644
index 0000000..2d49e98
--- /dev/null
+++ b/src/aosp/suggest/core/policy/weighting.h
@@ -0,0 +1,109 @@
+/*
+ * 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_WEIGHTING_H
+#define LATINIME_WEIGHTING_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicNode;
+class DicTraverseSession;
+struct DicNode_InputStateG;
+class MultiBigramMap;
+
+class Weighting {
+ public:
+    static void addCostAndForwardInputIndex(const Weighting *const weighting,
+            const CorrectionType correctionType,
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const parentDicNode, DicNode *const dicNode,
+            MultiBigramMap *const multiBigramMap);
+
+ protected:
+    virtual float getTerminalSpatialCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+
+    virtual float getOmissionCost(
+         const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
+
+    virtual float getMatchedCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
+            DicNode_InputStateG *inputStateG) const = 0;
+
+    virtual bool isProximityDicNode(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+
+    virtual float getTranspositionCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const parentDicNode,
+            const DicNode *const dicNode) const = 0;
+
+    virtual float getInsertionCost(
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
+
+    virtual float getNewWordSpatialCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode, DicNode_InputStateG *const inputStateG) const = 0;
+
+    virtual float getNewWordBigramLanguageCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
+            MultiBigramMap *const multiBigramMap) const = 0;
+
+    virtual float getCompletionCost(
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+
+    virtual float getTerminalInsertionCost(
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+
+    virtual float getTerminalLanguageCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
+            float dicNodeLanguageImprobability) const = 0;
+
+    virtual bool needsToNormalizeCompoundDistance() const = 0;
+
+    virtual float getAdditionalProximityCost() const = 0;
+
+    virtual float getSubstitutionCost() const = 0;
+
+    virtual float getSpaceSubstitutionCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+
+    virtual ErrorType getErrorType(const CorrectionType correctionType,
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
+
+    Weighting() {}
+    virtual ~Weighting() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(Weighting);
+
+    static float getSpatialCost(const Weighting *const weighting,
+            const CorrectionType correctionType, const DicTraverseSession *const traverseSession,
+            const DicNode *const parentDicNode, const DicNode *const dicNode,
+            DicNode_InputStateG *const inputStateG);
+    static float getLanguageCost(const Weighting *const weighting,
+            const CorrectionType correctionType, const DicTraverseSession *const traverseSession,
+            const DicNode *const parentDicNode, const DicNode *const dicNode,
+            MultiBigramMap *const multiBigramMap);
+    // TODO: Move to TypingWeighting and GestureWeighting?
+    static int getForwardInputCount(const CorrectionType correctionType);
+};
+} // namespace latinime
+#endif // LATINIME_WEIGHTING_H
diff --git a/src/aosp/suggest/core/session/dic_traverse_session.cpp b/src/aosp/suggest/core/session/dic_traverse_session.cpp
new file mode 100644
index 0000000..50f2bbd
--- /dev/null
+++ b/src/aosp/suggest/core/session/dic_traverse_session.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/session/dic_traverse_session.h"
+
+#include "defines.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+
+namespace latinime {
+
+// 256K bytes threshold is heuristically used to distinguish dictionaries containing many unigrams
+// (e.g. main dictionary) from small dictionaries (e.g. contacts...)
+const int DicTraverseSession::DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION =
+        256 * 1024;
+
+void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord,
+        int prevWordLength, const SuggestOptions *const suggestOptions) {
+    mDictionary = dictionary;
+    mMultiWordCostMultiplier = getDictionaryStructurePolicy()->getHeaderStructurePolicy()
+            ->getMultiWordCostMultiplier();
+    mSuggestOptions = suggestOptions;
+    if (!prevWord) {
+        mPrevWordPos = NOT_A_DICT_POS;
+        return;
+    }
+    // TODO: merge following similar calls to getTerminalPosition into one case-insensitive call.
+    mPrevWordPos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(
+            prevWord, prevWordLength, false /* forceLowerCaseSearch */);
+    if (mPrevWordPos == NOT_A_DICT_POS) {
+        // Check bigrams for lower-cased previous word if original was not found. Useful for
+        // auto-capitalized words like "The [current_word]".
+        mPrevWordPos = getDictionaryStructurePolicy()->getTerminalNodePositionOfWord(
+                prevWord, prevWordLength, true /* forceLowerCaseSearch */);
+    }
+}
+
+void DicTraverseSession::setupForGetSuggestions(const ProximityInfo *pInfo,
+        const int *inputCodePoints, const int inputSize, const int *const inputXs,
+        const int *const inputYs, const int *const times, const int *const pointerIds,
+        const float maxSpatialDistance, const int maxPointerCount) {
+    mProximityInfo = pInfo;
+    mMaxPointerCount = maxPointerCount;
+    initializeProximityInfoStates(inputCodePoints, inputXs, inputYs, times, pointerIds, inputSize,
+            maxSpatialDistance, maxPointerCount);
+}
+
+const DictionaryStructureWithBufferPolicy *DicTraverseSession::getDictionaryStructurePolicy()
+        const {
+    return mDictionary->getDictionaryStructurePolicy();
+}
+
+void DicTraverseSession::resetCache(const int thresholdForNextActiveDicNodes, const int maxWords) {
+    mDicNodesCache.reset(thresholdForNextActiveDicNodes /* nextActiveSize */,
+            maxWords /* terminalSize */);
+    mMultiBigramMap.clear();
+    mPartiallyCommited = false;
+}
+
+void DicTraverseSession::initializeProximityInfoStates(const int *const inputCodePoints,
+        const int *const inputXs, const int *const inputYs, const int *const times,
+        const int *const pointerIds, const int inputSize, const float maxSpatialDistance,
+        const int maxPointerCount) {
+    ASSERT(1 <= maxPointerCount && maxPointerCount <= MAX_POINTER_COUNT_G);
+    mInputSize = 0;
+    for (int i = 0; i < maxPointerCount; ++i) {
+        mProximityInfoStates[i].initInputParams(i, maxSpatialDistance, getProximityInfo(),
+                inputCodePoints, inputSize, inputXs, inputYs, times, pointerIds,
+                maxPointerCount == MAX_POINTER_COUNT_G
+                /* TODO: this is a hack. fix proximity info state */);
+        mInputSize += mProximityInfoStates[i].size();
+    }
+}
+} // namespace latinime
diff --git a/src/aosp/suggest/core/session/dic_traverse_session.h b/src/aosp/suggest/core/session/dic_traverse_session.h
new file mode 100644
index 0000000..e0b1c67
--- /dev/null
+++ b/src/aosp/suggest/core/session/dic_traverse_session.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_TRAVERSE_SESSION_H
+#define LATINIME_DIC_TRAVERSE_SESSION_H
+
+#include <stdint.h>
+#include <vector>
+
+#include "defines.h"
+#include "jni.h"
+#include "suggest/core/dicnode/dic_nodes_cache.h"
+#include "suggest/core/dictionary/multi_bigram_map.h"
+#include "suggest/core/layout/proximity_info_state.h"
+
+namespace latinime {
+
+class Dictionary;
+class DictionaryStructureWithBufferPolicy;
+class ProximityInfo;
+class SuggestOptions;
+
+class DicTraverseSession {
+ public:
+
+    // A factory method for DicTraverseSession
+    static AK_FORCE_INLINE void *getSessionInstance(JNIEnv *env, jstring localeStr,
+            jlong dictSize) {
+        // To deal with the trade-off between accuracy and memory space, large cache is used for
+        // dictionaries larger that the threshold
+        return new DicTraverseSession(env, localeStr,
+                dictSize >= DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION);
+    }
+
+    static AK_FORCE_INLINE void initSessionInstance(DicTraverseSession *traverseSession,
+            const Dictionary *const dictionary, const int *prevWord, const int prevWordLength,
+            const SuggestOptions *const suggestOptions) {
+        if (traverseSession) {
+            DicTraverseSession *tSession = static_cast<DicTraverseSession *>(traverseSession);
+            tSession->init(dictionary, prevWord, prevWordLength, suggestOptions);
+        }
+    }
+
+    static AK_FORCE_INLINE void releaseSessionInstance(DicTraverseSession *traverseSession) {
+        delete traverseSession;
+    }
+
+    AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr, bool usesLargeCache)
+            : mPrevWordPos(NOT_A_DICT_POS), mProximityInfo(0),
+              mDictionary(0), mSuggestOptions(0), mDicNodesCache(usesLargeCache),
+              mMultiBigramMap(), mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
+              mMultiWordCostMultiplier(1.0f) {
+        // NOTE: mProximityInfoStates is an array of instances.
+        // No need to initialize it explicitly here.
+    }
+
+    // Non virtual inline destructor -- never inherit this class
+    AK_FORCE_INLINE ~DicTraverseSession() {}
+
+    void init(const Dictionary *dictionary, const int *prevWord, int prevWordLength,
+            const SuggestOptions *const suggestOptions);
+    // TODO: Remove and merge into init
+    void setupForGetSuggestions(const ProximityInfo *pInfo, const int *inputCodePoints,
+            const int inputSize, const int *const inputXs, const int *const inputYs,
+            const int *const times, const int *const pointerIds, const float maxSpatialDistance,
+            const int maxPointerCount);
+    void resetCache(const int thresholdForNextActiveDicNodes, const int maxWords);
+
+    const DictionaryStructureWithBufferPolicy *getDictionaryStructurePolicy() const;
+
+    //--------------------
+    // getters and setters
+    //--------------------
+    const ProximityInfo *getProximityInfo() const { return mProximityInfo; }
+    const SuggestOptions *getSuggestOptions() const { return mSuggestOptions; }
+    int getPrevWordPos() const { return mPrevWordPos; }
+    // TODO: REMOVE
+    void setPrevWordPos(int pos) { mPrevWordPos = pos; }
+    // TODO: Use proper parameter when changed
+    int getDicRootPos() const { return 0; }
+    DicNodesCache *getDicTraverseCache() { return &mDicNodesCache; }
+    MultiBigramMap *getMultiBigramMap() { return &mMultiBigramMap; }
+    const ProximityInfoState *getProximityInfoState(int id) const {
+        return &mProximityInfoStates[id];
+    }
+    int getInputSize() const { return mInputSize; }
+    void setPartiallyCommited() { mPartiallyCommited = true; }
+    bool isPartiallyCommited() const { return mPartiallyCommited; }
+
+    bool isOnlyOnePointerUsed(int *pointerId) const {
+        // Not in the dictionary word
+        int usedPointerCount = 0;
+        int usedPointerId = 0;
+        for (int i = 0; i < mMaxPointerCount; ++i) {
+            if (mProximityInfoStates[i].isUsed()) {
+                ++usedPointerCount;
+                usedPointerId = i;
+            }
+        }
+        if (usedPointerCount != 1) {
+            return false;
+        }
+        if (pointerId) {
+            *pointerId = usedPointerId;
+        }
+        return true;
+    }
+
+    void getSearchKeys(const DicNode *node, std::vector<int> *const outputSearchKeyVector) const {
+        for (int i = 0; i < MAX_POINTER_COUNT_G; ++i) {
+            if (!mProximityInfoStates[i].isUsed()) {
+                continue;
+            }
+            const int pointerId = node->getInputIndex(i);
+            const std::vector<int> *const searchKeyVector =
+                    mProximityInfoStates[i].getSearchKeyVector(pointerId);
+            outputSearchKeyVector->insert(outputSearchKeyVector->end(), searchKeyVector->begin(),
+                    searchKeyVector->end());
+        }
+    }
+
+    ProximityType getProximityTypeG(const DicNode *const node, const int childCodePoint) const {
+        ProximityType proximityType = UNRELATED_CHAR;
+        for (int i = 0; i < MAX_POINTER_COUNT_G; ++i) {
+            if (!mProximityInfoStates[i].isUsed()) {
+                continue;
+            }
+            const int pointerId = node->getInputIndex(i);
+            proximityType = mProximityInfoStates[i].getProximityTypeG(pointerId, childCodePoint);
+            ASSERT(proximityType == UNRELATED_CHAR || proximityType == MATCH_CHAR);
+            // TODO: Make this more generic
+            // Currently we assume there are only two types here -- UNRELATED_CHAR
+            // and MATCH_CHAR
+            if (proximityType != UNRELATED_CHAR) {
+                return proximityType;
+            }
+        }
+        return proximityType;
+    }
+
+    AK_FORCE_INLINE bool isCacheBorderForTyping(const int inputSize) const {
+        return mDicNodesCache.isCacheBorderForTyping(inputSize);
+    }
+
+    /**
+     * Returns whether or not it is possible to continue suggestion from the previous search.
+     */
+    // TODO: Remove. No need to check once the session is fully implemented.
+    bool isContinuousSuggestionPossible() const {
+        if (!mDicNodesCache.hasCachedDicNodesForContinuousSuggestion()) {
+            return false;
+        }
+        ASSERT(mMaxPointerCount <= MAX_POINTER_COUNT_G);
+        for (int i = 0; i < mMaxPointerCount; ++i) {
+            const ProximityInfoState *const pInfoState = getProximityInfoState(i);
+            // If a proximity info state is not continuous suggestion possible,
+            // do not continue searching.
+            if (pInfoState->isUsed() && !pInfoState->isContinuousSuggestionPossible()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    bool isTouchPositionCorrectionEnabled() const {
+        return mProximityInfoStates[0].touchPositionCorrectionEnabled();
+    }
+
+    float getMultiWordCostMultiplier() const {
+        return mMultiWordCostMultiplier;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseSession);
+    // threshold to start caching
+    static const int CACHE_START_INPUT_LENGTH_THRESHOLD;
+    static const int DICTIONARY_SIZE_THRESHOLD_TO_USE_LARGE_CACHE_FOR_SUGGESTION;
+    void initializeProximityInfoStates(const int *const inputCodePoints, const int *const inputXs,
+            const int *const inputYs, const int *const times, const int *const pointerIds,
+            const int inputSize, const float maxSpatialDistance, const int maxPointerCount);
+
+    int mPrevWordPos;
+    const ProximityInfo *mProximityInfo;
+    const Dictionary *mDictionary;
+    const SuggestOptions *mSuggestOptions;
+
+    DicNodesCache mDicNodesCache;
+    // Temporary cache for bigram frequencies
+    MultiBigramMap mMultiBigramMap;
+    ProximityInfoState mProximityInfoStates[MAX_POINTER_COUNT_G];
+
+    int mInputSize;
+    bool mPartiallyCommited;
+    int mMaxPointerCount;
+
+    /////////////////////////////////
+    // Configuration per dictionary
+    float mMultiWordCostMultiplier;
+
+};
+} // namespace latinime
+#endif // LATINIME_DIC_TRAVERSE_SESSION_H
diff --git a/src/aosp/suggest/core/suggest.cpp b/src/aosp/suggest/core/suggest.cpp
new file mode 100644
index 0000000..73ccebc
--- /dev/null
+++ b/src/aosp/suggest/core/suggest.cpp
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/suggest.h"
+
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_priority_queue.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/binary_dictionary_shortcut_iterator.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/dictionary/digraph_utils.h"
+#include "suggest/core/dictionary/shortcut_utils.h"
+#include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "suggest/core/policy/scoring.h"
+#include "suggest/core/policy/traversal.h"
+#include "suggest/core/policy/weighting.h"
+#include "suggest/core/session/dic_traverse_session.h"
+
+namespace latinime {
+
+// Initialization of class constants.
+const int Suggest::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16;
+const int Suggest::MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE = 2;
+const float Suggest::AUTOCORRECT_CLASSIFICATION_THRESHOLD = 0.33f;
+
+/**
+ * Returns a set of suggestions for the given input touch points. The commitPoint argument indicates
+ * whether to prematurely commit the suggested words up to the given point for sentence-level
+ * suggestion.
+ *
+ * Note: Currently does not support concurrent calls across threads. Continuous suggestion is
+ * automatically activated for sequential calls that share the same starting input.
+ * TODO: Stop detecting continuous suggestion. Start using traverseSession instead.
+ */
+int Suggest::getSuggestions(ProximityInfo *pInfo, void *traverseSession,
+        int *inputXs, int *inputYs, int *times, int *pointerIds, int *inputCodePoints,
+        int inputSize, int commitPoint, int *outWords, int *frequencies, int *outputIndices,
+        int *outputTypes, int *outputAutoCommitFirstWordConfidence) const {
+    PROF_OPEN;
+    PROF_START(0);
+    const float maxSpatialDistance = TRAVERSAL->getMaxSpatialDistance();
+    DicTraverseSession *tSession = static_cast<DicTraverseSession *>(traverseSession);
+    tSession->setupForGetSuggestions(pInfo, inputCodePoints, inputSize, inputXs, inputYs, times,
+            pointerIds, maxSpatialDistance, TRAVERSAL->getMaxPointerCount());
+    // TODO: Add the way to evaluate cache
+
+    initializeSearch(tSession, commitPoint);
+    PROF_END(0);
+    PROF_START(1);
+
+    // keep expanding search dicNodes until all have terminated.
+    while (tSession->getDicTraverseCache()->activeSize() > 0) {
+        expandCurrentDicNodes(tSession);
+        tSession->getDicTraverseCache()->advanceActiveDicNodes();
+        tSession->getDicTraverseCache()->advanceInputIndex(inputSize);
+    }
+    PROF_END(1);
+    PROF_START(2);
+    const int size = outputSuggestions(tSession, frequencies, outWords, outputIndices, outputTypes,
+            outputAutoCommitFirstWordConfidence);
+    PROF_END(2);
+    PROF_CLOSE;
+    return size;
+}
+
+/**
+ * Initializes the search at the root of the lexicon trie. Note that when possible the search will
+ * continue suggestion from where it left off during the last call.
+ */
+void Suggest::initializeSearch(DicTraverseSession *traverseSession, int commitPoint) const {
+    if (!traverseSession->getProximityInfoState(0)->isUsed()) {
+        return;
+    }
+
+    // Never auto partial commit for now.
+    commitPoint = 0;
+
+    if (traverseSession->getInputSize() > MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE
+            && traverseSession->isContinuousSuggestionPossible()) {
+        if (commitPoint == 0) {
+            // Continue suggestion
+            traverseSession->getDicTraverseCache()->continueSearch();
+        } else {
+            // Continue suggestion after partial commit.
+            DicNode *topDicNode =
+                    traverseSession->getDicTraverseCache()->setCommitPoint(commitPoint);
+            traverseSession->setPrevWordPos(topDicNode->getPrevWordNodePos());
+            traverseSession->getDicTraverseCache()->continueSearch();
+            traverseSession->setPartiallyCommited();
+        }
+    } else {
+        // Restart recognition at the root.
+        traverseSession->resetCache(TRAVERSAL->getMaxCacheSize(traverseSession->getInputSize()),
+                MAX_RESULTS);
+        // Create a new dic node here
+        DicNode rootNode;
+        DicNodeUtils::initAsRoot(traverseSession->getDictionaryStructurePolicy(),
+                traverseSession->getPrevWordPos(), &rootNode);
+        traverseSession->getDicTraverseCache()->copyPushActive(&rootNode);
+    }
+}
+
+/**
+ * Outputs the final list of suggestions (i.e., terminal nodes).
+ */
+int Suggest::outputSuggestions(DicTraverseSession *traverseSession, int *frequencies,
+        int *outputCodePoints, int *outputIndicesToPartialCommit, int *outputTypes,
+        int *outputAutoCommitFirstWordConfidence) const {
+#if DEBUG_EVALUATE_MOST_PROBABLE_STRING
+    const int terminalSize = 0;
+#else
+    const int terminalSize = min(MAX_RESULTS,
+            static_cast<int>(traverseSession->getDicTraverseCache()->terminalSize()));
+#endif
+    DicNode terminals[MAX_RESULTS]; // Avoiding non-POD variable length array
+
+    for (int index = terminalSize - 1; index >= 0; --index) {
+        traverseSession->getDicTraverseCache()->popTerminal(&terminals[index]);
+    }
+
+    const float languageWeight = SCORING->getAdjustedLanguageWeight(
+            traverseSession, terminals, terminalSize);
+
+    int outputWordIndex = 0;
+    // Insert most probable word at index == 0 as long as there is one terminal at least
+    const bool hasMostProbableString =
+            SCORING->getMostProbableString(traverseSession, terminalSize, languageWeight,
+                    &outputCodePoints[0], &outputTypes[0], &frequencies[0]);
+    if (hasMostProbableString) {
+        outputIndicesToPartialCommit[outputWordIndex] = NOT_AN_INDEX;
+        ++outputWordIndex;
+    }
+
+    // Initial value of the loop index for terminal nodes (words)
+    int doubleLetterTerminalIndex = -1;
+    DoubleLetterLevel doubleLetterLevel = NOT_A_DOUBLE_LETTER;
+    SCORING->searchWordWithDoubleLetter(terminals, terminalSize,
+            &doubleLetterTerminalIndex, &doubleLetterLevel);
+
+    int maxScore = S_INT_MIN;
+    // Force autocorrection for obvious long multi-word suggestions when the top suggestion is
+    // a long multiple words suggestion.
+    // TODO: Implement a smarter auto-commit method for handling multi-word suggestions.
+    // traverseSession->isPartiallyCommited() always returns false because we never auto partial
+    // commit for now.
+    const bool forceCommitMultiWords = (terminalSize > 0) ?
+            TRAVERSAL->autoCorrectsToMultiWordSuggestionIfTop()
+                    && (traverseSession->isPartiallyCommited()
+                            || (traverseSession->getInputSize()
+                                    >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT
+                                            && terminals[0].hasMultipleWords())) : false;
+    // TODO: have partial commit work even with multiple pointers.
+    const bool outputSecondWordFirstLetterInputIndex =
+            traverseSession->isOnlyOnePointerUsed(0 /* pointerId */);
+    if (terminalSize > 0) {
+        // If we have no suggestions, don't write this
+        outputAutoCommitFirstWordConfidence[0] =
+                computeFirstWordConfidence(&terminals[0]);
+    }
+
+    // Output suggestion results here
+    for (int terminalIndex = 0; terminalIndex < terminalSize && outputWordIndex < MAX_RESULTS;
+            ++terminalIndex) {
+        DicNode *terminalDicNode = &terminals[terminalIndex];
+        if (DEBUG_GEO_FULL) {
+            terminalDicNode->dump("OUT:");
+        }
+        const float doubleLetterCost = SCORING->getDoubleLetterDemotionDistanceCost(
+                terminalIndex, doubleLetterTerminalIndex, doubleLetterLevel);
+        const float compoundDistance = terminalDicNode->getCompoundDistance(languageWeight)
+                + doubleLetterCost;
+        const bool isPossiblyOffensiveWord =
+                traverseSession->getDictionaryStructurePolicy()->getProbability(
+                        terminalDicNode->getProbability(), NOT_A_PROBABILITY) <= 0;
+        const bool isExactMatch = terminalDicNode->isExactMatch();
+        const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase();
+        // Heuristic: We exclude freq=0 first-char-uppercase words from exact match.
+        // (e.g. "AMD" and "and")
+        const bool isSafeExactMatch = isExactMatch
+                && !(isPossiblyOffensiveWord && isFirstCharUppercase);
+        const int outputTypeFlags =
+                (isPossiblyOffensiveWord ? Dictionary::KIND_FLAG_POSSIBLY_OFFENSIVE : 0)
+                | (isSafeExactMatch ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
+
+        // Entries that are blacklisted or do not represent a word should not be output.
+        const bool isValidWord = !terminalDicNode->isBlacklistedOrNotAWord();
+
+        // Increase output score of top typing suggestion to ensure autocorrection.
+        // TODO: Better integration with java side autocorrection logic.
+        const int finalScore = SCORING->calculateFinalScore(
+                compoundDistance, traverseSession->getInputSize(),
+                terminalDicNode->isExactMatch()
+                        || (forceCommitMultiWords && terminalDicNode->hasMultipleWords())
+                                || (isValidWord && SCORING->doesAutoCorrectValidWord()));
+        if (maxScore < finalScore && isValidWord) {
+            maxScore = finalScore;
+        }
+
+        // Don't output invalid words. However, we still need to submit their shortcuts if any.
+        if (isValidWord) {
+            outputTypes[outputWordIndex] = Dictionary::KIND_CORRECTION | outputTypeFlags;
+            frequencies[outputWordIndex] = finalScore;
+            if (outputSecondWordFirstLetterInputIndex) {
+                outputIndicesToPartialCommit[outputWordIndex] =
+                        terminalDicNode->getSecondWordFirstInputIndex(
+                                traverseSession->getProximityInfoState(0));
+            } else {
+                outputIndicesToPartialCommit[outputWordIndex] = NOT_AN_INDEX;
+            }
+            // Populate the outputChars array with the suggested word.
+            const int startIndex = outputWordIndex * MAX_WORD_LENGTH;
+            terminalDicNode->outputResult(&outputCodePoints[startIndex]);
+            ++outputWordIndex;
+        }
+
+        if (!terminalDicNode->hasMultipleWords()) {
+            BinaryDictionaryShortcutIterator shortcutIt(
+                    traverseSession->getDictionaryStructurePolicy()->getShortcutsStructurePolicy(),
+                    traverseSession->getDictionaryStructurePolicy()
+                            ->getShortcutPositionOfPtNode(terminalDicNode->getPos()));
+            // Shortcut is not supported for multiple words suggestions.
+            // TODO: Check shortcuts during traversal for multiple words suggestions.
+            const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);
+            const int updatedOutputWordIndex = ShortcutUtils::outputShortcuts(&shortcutIt,
+                    outputWordIndex,  finalScore, outputCodePoints, frequencies, outputTypes,
+                    sameAsTyped);
+            const int secondWordFirstInputIndex = terminalDicNode->getSecondWordFirstInputIndex(
+                    traverseSession->getProximityInfoState(0));
+            for (int i = outputWordIndex; i < updatedOutputWordIndex; ++i) {
+                if (outputSecondWordFirstLetterInputIndex) {
+                    outputIndicesToPartialCommit[i] = secondWordFirstInputIndex;
+                } else {
+                    outputIndicesToPartialCommit[i] = NOT_AN_INDEX;
+                }
+            }
+            outputWordIndex = updatedOutputWordIndex;
+        }
+        DicNode::managedDelete(terminalDicNode);
+    }
+
+    if (hasMostProbableString) {
+        SCORING->safetyNetForMostProbableString(terminalSize, maxScore,
+                &outputCodePoints[0], &frequencies[0]);
+    }
+    return outputWordIndex;
+}
+
+int Suggest::computeFirstWordConfidence(const DicNode *const terminalDicNode) const {
+    // Get the number of spaces in the first suggestion
+    const int spaceCount = terminalDicNode->getTotalNodeSpaceCount();
+    // Get the number of characters in the first suggestion
+    const int length = terminalDicNode->getTotalNodeCodePointCount();
+    // Get the distance for the first word of the suggestion
+    const float distance = terminalDicNode->getNormalizedCompoundDistanceAfterFirstWord();
+
+    // Arbitrarily, we give a score whose useful values range from 0 to 1,000,000.
+    // 1,000,000 will be the cutoff to auto-commit. It's fine if the number is under 0 or
+    // above 1,000,000 : under 0 just means it's very bad to commit, and above 1,000,000 means
+    // we are very confident.
+    // Expected space count is 1 ~ 5
+    static const int MIN_EXPECTED_SPACE_COUNT = 1;
+    static const int MAX_EXPECTED_SPACE_COUNT = 5;
+    // Expected length is about 4 ~ 30
+    static const int MIN_EXPECTED_LENGTH = 4;
+    static const int MAX_EXPECTED_LENGTH = 30;
+    // Expected distance is about 0.2 ~ 2.0, but consider 0.0 ~ 2.0
+    static const float MIN_EXPECTED_DISTANCE = 0.0;
+    static const float MAX_EXPECTED_DISTANCE = 2.0;
+    // This is not strict: it's where most stuff will be falling, but it's still fine if it's
+    // outside these values. We want to output a value that reflects all of these. Each factor
+    // contributes a bit.
+
+    // We need at least a space.
+    if (spaceCount < 1) return NOT_A_FIRST_WORD_CONFIDENCE;
+
+    // The smaller the edit distance, the higher the contribution. MIN_EXPECTED_DISTANCE means 0
+    // contribution, while MAX_EXPECTED_DISTANCE means full contribution according to the
+    // weight of the distance. Clamp to avoid overflows.
+    const float clampedDistance = distance < MIN_EXPECTED_DISTANCE ? MIN_EXPECTED_DISTANCE
+            : distance > MAX_EXPECTED_DISTANCE ? MAX_EXPECTED_DISTANCE : distance;
+    const int distanceContribution = DISTANCE_WEIGHT_FOR_AUTO_COMMIT
+            * (MAX_EXPECTED_DISTANCE - clampedDistance)
+            / (MAX_EXPECTED_DISTANCE - MIN_EXPECTED_DISTANCE);
+    // The larger the suggestion length, the larger the contribution. MIN_EXPECTED_LENGTH is no
+    // contribution, MAX_EXPECTED_LENGTH is full contribution according to the weight of the
+    // length. Length is guaranteed to be between 1 and 48, so we don't need to clamp.
+    const int lengthContribution = LENGTH_WEIGHT_FOR_AUTO_COMMIT
+            * (length - MIN_EXPECTED_LENGTH) / (MAX_EXPECTED_LENGTH - MIN_EXPECTED_LENGTH);
+    // The more spaces, the larger the contribution. MIN_EXPECTED_SPACE_COUNT space is no
+    // contribution, MAX_EXPECTED_SPACE_COUNT spaces is full contribution according to the
+    // weight of the space count.
+    const int spaceContribution = SPACE_COUNT_WEIGHT_FOR_AUTO_COMMIT
+            * (spaceCount - MIN_EXPECTED_SPACE_COUNT)
+            / (MAX_EXPECTED_SPACE_COUNT - MIN_EXPECTED_SPACE_COUNT);
+
+    return distanceContribution + lengthContribution + spaceContribution;
+}
+
+/**
+ * Expands the dicNodes in the current search priority queue by advancing to the possible child
+ * nodes based on the next touch point(s) (or no touch points for lookahead)
+ */
+void Suggest::expandCurrentDicNodes(DicTraverseSession *traverseSession) const {
+    const int inputSize = traverseSession->getInputSize();
+    DicNodeVector childDicNodes(TRAVERSAL->getDefaultExpandDicNodeSize());
+    DicNode correctionDicNode;
+
+    // TODO: Find more efficient caching
+    const bool shouldDepthLevelCache = TRAVERSAL->shouldDepthLevelCache(traverseSession);
+    if (shouldDepthLevelCache) {
+        traverseSession->getDicTraverseCache()->updateLastCachedInputIndex();
+    }
+    if (DEBUG_CACHE) {
+        AKLOGI("expandCurrentDicNodes depth level cache = %d, inputSize = %d",
+                shouldDepthLevelCache, inputSize);
+    }
+    while (traverseSession->getDicTraverseCache()->activeSize() > 0) {
+        DicNode dicNode;
+        traverseSession->getDicTraverseCache()->popActive(&dicNode);
+        if (dicNode.isTotalInputSizeExceedingLimit()) {
+            return;
+        }
+        childDicNodes.clear();
+        const int point0Index = dicNode.getInputIndex(0);
+        const bool canDoLookAheadCorrection =
+                TRAVERSAL->canDoLookAheadCorrection(traverseSession, &dicNode);
+        const bool isLookAheadCorrection = canDoLookAheadCorrection
+                && traverseSession->getDicTraverseCache()->
+                        isLookAheadCorrectionInputIndex(static_cast<int>(point0Index));
+        const bool isCompletion = dicNode.isCompletion(inputSize);
+
+        const bool shouldNodeLevelCache =
+                TRAVERSAL->shouldNodeLevelCache(traverseSession, &dicNode);
+        if (shouldDepthLevelCache || shouldNodeLevelCache) {
+            if (DEBUG_CACHE) {
+                dicNode.dump("PUSH_CACHE");
+            }
+            traverseSession->getDicTraverseCache()->copyPushContinue(&dicNode);
+            dicNode.setCached();
+        }
+
+        if (dicNode.isInDigraph()) {
+            // Finish digraph handling if the node is in the middle of a digraph expansion.
+            processDicNodeAsDigraph(traverseSession, &dicNode);
+        } else if (isLookAheadCorrection) {
+            // The algorithm maintains a small set of "deferred" nodes that have not consumed the
+            // latest touch point yet. These are needed to apply look-ahead correction operations
+            // that require special handling of the latest touch point. For example, with insertions
+            // (e.g., "thiis" -> "this") the latest touch point should not be consumed at all.
+            processDicNodeAsTransposition(traverseSession, &dicNode);
+            processDicNodeAsInsertion(traverseSession, &dicNode);
+        } else { // !isLookAheadCorrection
+            // Only consider typing error corrections if the normalized compound distance is
+            // below a spatial distance threshold.
+            // NOTE: the threshold may need to be updated if scoring model changes.
+            // TODO: Remove. Do not prune node here.
+            const bool allowsErrorCorrections = TRAVERSAL->allowsErrorCorrections(&dicNode);
+            // Process for handling space substitution (e.g., hevis => he is)
+            if (allowsErrorCorrections
+                    && TRAVERSAL->isSpaceSubstitutionTerminal(traverseSession, &dicNode)) {
+                createNextWordDicNode(traverseSession, &dicNode, true /* spaceSubstitution */);
+            }
+
+            DicNodeUtils::getAllChildDicNodes(
+                    &dicNode, traverseSession->getDictionaryStructurePolicy(), &childDicNodes);
+
+            const int childDicNodesSize = childDicNodes.getSizeAndLock();
+            for (int i = 0; i < childDicNodesSize; ++i) {
+                DicNode *const childDicNode = childDicNodes[i];
+                if (isCompletion) {
+                    // Handle forward lookahead when the lexicon letter exceeds the input size.
+                    processDicNodeAsMatch(traverseSession, childDicNode);
+                    continue;
+                }
+                if (DigraphUtils::hasDigraphForCodePoint(
+                        traverseSession->getDictionaryStructurePolicy()
+                                ->getHeaderStructurePolicy(),
+                        childDicNode->getNodeCodePoint())) {
+                    correctionDicNode.initByCopy(childDicNode);
+                    correctionDicNode.advanceDigraphIndex();
+                    processDicNodeAsDigraph(traverseSession, &correctionDicNode);
+                }
+                if (TRAVERSAL->isOmission(traverseSession, &dicNode, childDicNode,
+                        allowsErrorCorrections)) {
+                    // TODO: (Gesture) Change weight between omission and substitution errors
+                    // TODO: (Gesture) Terminal node should not be handled as omission
+                    correctionDicNode.initByCopy(childDicNode);
+                    processDicNodeAsOmission(traverseSession, &correctionDicNode);
+                }
+                const ProximityType proximityType = TRAVERSAL->getProximityType(
+                        traverseSession, &dicNode, childDicNode);
+                switch (proximityType) {
+                    // TODO: Consider the difference of proximityType here
+                    case MATCH_CHAR:
+                    case PROXIMITY_CHAR:
+                        processDicNodeAsMatch(traverseSession, childDicNode);
+                        break;
+                    case ADDITIONAL_PROXIMITY_CHAR:
+                        if (allowsErrorCorrections) {
+                            processDicNodeAsAdditionalProximityChar(traverseSession, &dicNode,
+                                    childDicNode);
+                        }
+                        break;
+                    case SUBSTITUTION_CHAR:
+                        if (allowsErrorCorrections) {
+                            processDicNodeAsSubstitution(traverseSession, &dicNode, childDicNode);
+                        }
+                        break;
+                    case UNRELATED_CHAR:
+                        // Just drop this node and do nothing.
+                        break;
+                    default:
+                        // Just drop this node and do nothing.
+                        break;
+                }
+            }
+
+            // Push the node for look-ahead correction
+            if (allowsErrorCorrections && canDoLookAheadCorrection) {
+                traverseSession->getDicTraverseCache()->copyPushNextActive(&dicNode);
+            }
+        }
+    }
+}
+
+void Suggest::processTerminalDicNode(
+        DicTraverseSession *traverseSession, DicNode *dicNode) const {
+    if (dicNode->getCompoundDistance() >= static_cast<float>(MAX_VALUE_FOR_WEIGHTING)) {
+        return;
+    }
+    if (!dicNode->isTerminalWordNode()) {
+        return;
+    }
+    if (dicNode->shouldBeFilteredBySafetyNetForBigram()) {
+        return;
+    }
+    // Create a non-cached node here.
+    DicNode terminalDicNode;
+    DicNodeUtils::initByCopy(dicNode, &terminalDicNode);
+    if (TRAVERSAL->needsToTraverseAllUserInput()
+            && dicNode->getInputIndex(0) < traverseSession->getInputSize()) {
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TERMINAL_INSERTION, traverseSession, 0,
+                &terminalDicNode, traverseSession->getMultiBigramMap());
+    }
+    Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TERMINAL, traverseSession, 0,
+            &terminalDicNode, traverseSession->getMultiBigramMap());
+    traverseSession->getDicTraverseCache()->copyPushTerminal(&terminalDicNode);
+}
+
+/**
+ * Adds the expanded dicNode to the next search priority queue. Also creates an additional next word
+ * (by the space omission error correction) search path if input dicNode is on a terminal node.
+ */
+void Suggest::processExpandedDicNode(
+        DicTraverseSession *traverseSession, DicNode *dicNode) const {
+    processTerminalDicNode(traverseSession, dicNode);
+    if (dicNode->getCompoundDistance() < static_cast<float>(MAX_VALUE_FOR_WEIGHTING)) {
+        if (TRAVERSAL->isSpaceOmissionTerminal(traverseSession, dicNode)) {
+            createNextWordDicNode(traverseSession, dicNode, false /* spaceSubstitution */);
+        }
+        const int allowsLookAhead = !(dicNode->hasMultipleWords()
+                && dicNode->isCompletion(traverseSession->getInputSize()));
+        if (dicNode->hasChildren() && allowsLookAhead) {
+            traverseSession->getDicTraverseCache()->copyPushNextActive(dicNode);
+        }
+    }
+    DicNode::managedDelete(dicNode);
+}
+
+void Suggest::processDicNodeAsMatch(DicTraverseSession *traverseSession,
+        DicNode *childDicNode) const {
+    weightChildNode(traverseSession, childDicNode);
+    processExpandedDicNode(traverseSession, childDicNode);
+}
+
+void Suggest::processDicNodeAsAdditionalProximityChar(DicTraverseSession *traverseSession,
+        DicNode *dicNode, DicNode *childDicNode) const {
+    // Note: Most types of corrections don't need to look up the bigram information since they do
+    // not treat the node as a terminal. There is no need to pass the bigram map in these cases.
+    Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_ADDITIONAL_PROXIMITY,
+            traverseSession, dicNode, childDicNode, 0 /* multiBigramMap */);
+    weightChildNode(traverseSession, childDicNode);
+    processExpandedDicNode(traverseSession, childDicNode);
+}
+
+void Suggest::processDicNodeAsSubstitution(DicTraverseSession *traverseSession,
+        DicNode *dicNode, DicNode *childDicNode) const {
+    Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_SUBSTITUTION, traverseSession,
+            dicNode, childDicNode, 0 /* multiBigramMap */);
+    weightChildNode(traverseSession, childDicNode);
+    processExpandedDicNode(traverseSession, childDicNode);
+}
+
+// Process the node codepoint as a digraph. This means that composite glyphs like the German
+// u-umlaut is expanded to the transliteration "ue". Note that this happens in parallel with
+// the normal non-digraph traversal, so both "uber" and "ueber" can be corrected to "[u-umlaut]ber".
+void Suggest::processDicNodeAsDigraph(DicTraverseSession *traverseSession,
+        DicNode *childDicNode) const {
+    weightChildNode(traverseSession, childDicNode);
+    childDicNode->advanceDigraphIndex();
+    processExpandedDicNode(traverseSession, childDicNode);
+}
+
+/**
+ * Handle the dicNode as an omission error (e.g., ths => this). Skip the current letter and consider
+ * matches for all possible next letters. Note that just skipping the current letter without any
+ * other conditions tends to flood the search dic nodes cache with omission nodes. Instead, check
+ * the possible *next* letters after the omission to better limit search to plausible omissions.
+ * Note that apostrophes are handled as omissions.
+ */
+void Suggest::processDicNodeAsOmission(
+        DicTraverseSession *traverseSession, DicNode *dicNode) const {
+    DicNodeVector childDicNodes;
+    DicNodeUtils::getAllChildDicNodes(
+            dicNode, traverseSession->getDictionaryStructurePolicy(), &childDicNodes);
+
+    const int size = childDicNodes.getSizeAndLock();
+    for (int i = 0; i < size; i++) {
+        DicNode *const childDicNode = childDicNodes[i];
+        // Treat this word as omission
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_OMISSION, traverseSession,
+                dicNode, childDicNode, 0 /* multiBigramMap */);
+        weightChildNode(traverseSession, childDicNode);
+        if (!TRAVERSAL->isPossibleOmissionChildNode(traverseSession, dicNode, childDicNode)) {
+            continue;
+        }
+        processExpandedDicNode(traverseSession, childDicNode);
+    }
+}
+
+/**
+ * Handle the dicNode as an insertion error (e.g., thiis => this). Skip the current touch point and
+ * consider matches for the next touch point.
+ */
+void Suggest::processDicNodeAsInsertion(DicTraverseSession *traverseSession,
+        DicNode *dicNode) const {
+    const int16_t pointIndex = dicNode->getInputIndex(0);
+    DicNodeVector childDicNodes;
+    DicNodeUtils::getAllChildDicNodes(dicNode, traverseSession->getDictionaryStructurePolicy(),
+            &childDicNodes);
+    const int size = childDicNodes.getSizeAndLock();
+    for (int i = 0; i < size; i++) {
+        if (traverseSession->getProximityInfoState(0)->getPrimaryCodePointAt(pointIndex + 1)
+                != childDicNodes[i]->getNodeCodePoint()) {
+            continue;
+        }
+        DicNode *const childDicNode = childDicNodes[i];
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_INSERTION, traverseSession,
+                dicNode, childDicNode, 0 /* multiBigramMap */);
+        processExpandedDicNode(traverseSession, childDicNode);
+    }
+}
+
+/**
+ * Handle the dicNode as a transposition error (e.g., thsi => this). Swap the next two touch points.
+ */
+void Suggest::processDicNodeAsTransposition(DicTraverseSession *traverseSession,
+        DicNode *dicNode) const {
+    const int16_t pointIndex = dicNode->getInputIndex(0);
+    DicNodeVector childDicNodes1;
+    DicNodeUtils::getAllChildDicNodes(dicNode, traverseSession->getDictionaryStructurePolicy(),
+            &childDicNodes1);
+    const int childSize1 = childDicNodes1.getSizeAndLock();
+    for (int i = 0; i < childSize1; i++) {
+        const ProximityType matchedId1 = traverseSession->getProximityInfoState(0)
+                ->getProximityType(pointIndex + 1, childDicNodes1[i]->getNodeCodePoint(),
+                        true /* checkProximityChars */);
+        if (!ProximityInfoUtils::isMatchOrProximityChar(matchedId1)) {
+            continue;
+        }
+        if (childDicNodes1[i]->hasChildren()) {
+            DicNodeVector childDicNodes2;
+            DicNodeUtils::getAllChildDicNodes(childDicNodes1[i],
+                    traverseSession->getDictionaryStructurePolicy(), &childDicNodes2);
+            const int childSize2 = childDicNodes2.getSizeAndLock();
+            for (int j = 0; j < childSize2; j++) {
+                DicNode *const childDicNode2 = childDicNodes2[j];
+                const ProximityType matchedId2 = traverseSession->getProximityInfoState(0)
+                        ->getProximityType(pointIndex, childDicNode2->getNodeCodePoint(),
+                                true /* checkProximityChars */);
+                if (!ProximityInfoUtils::isMatchOrProximityChar(matchedId2)) {
+                    continue;
+                }
+                Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TRANSPOSITION,
+                        traverseSession, childDicNodes1[i], childDicNode2, 0 /* multiBigramMap */);
+                processExpandedDicNode(traverseSession, childDicNode2);
+            }
+        }
+        DicNode::managedDelete(childDicNodes1[i]);
+    }
+}
+
+/**
+ * Weight child node by aligning it to the key
+ */
+void Suggest::weightChildNode(DicTraverseSession *traverseSession, DicNode *dicNode) const {
+    const int inputSize = traverseSession->getInputSize();
+    if (dicNode->isCompletion(inputSize)) {
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_COMPLETION, traverseSession,
+                0 /* parentDicNode */, dicNode, 0 /* multiBigramMap */);
+    } else { // completion
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_MATCH, traverseSession,
+                0 /* parentDicNode */, dicNode, 0 /* multiBigramMap */);
+    }
+}
+
+/**
+ * Creates a new dicNode that represents a space insertion at the end of the input dicNode. Also
+ * incorporates the unigram / bigram score for the ending word into the new dicNode.
+ */
+void Suggest::createNextWordDicNode(DicTraverseSession *traverseSession, DicNode *dicNode,
+        const bool spaceSubstitution) const {
+    if (!TRAVERSAL->isGoodToTraverseNextWord(dicNode)) {
+        return;
+    }
+
+    // Create a non-cached node here.
+    DicNode newDicNode;
+    DicNodeUtils::initAsRootWithPreviousWord(
+            traverseSession->getDictionaryStructurePolicy(), dicNode, &newDicNode);
+    const CorrectionType correctionType = spaceSubstitution ?
+            CT_NEW_WORD_SPACE_SUBSTITUTION : CT_NEW_WORD_SPACE_OMISSION;
+    Weighting::addCostAndForwardInputIndex(WEIGHTING, correctionType, traverseSession, dicNode,
+            &newDicNode, traverseSession->getMultiBigramMap());
+    if (newDicNode.getCompoundDistance() < static_cast<float>(MAX_VALUE_FOR_WEIGHTING)) {
+        // newDicNode is worth continuing to traverse.
+        // CAVEAT: This pruning is important for speed. Remove this when we can afford not to prune
+        // here because here is not the right place to do pruning. Pruning should take place only
+        // in DicNodePriorityQueue.
+        traverseSession->getDicTraverseCache()->copyPushNextActive(&newDicNode);
+    }
+}
+} // namespace latinime
diff --git a/src/aosp/suggest/core/suggest.h b/src/aosp/suggest/core/suggest.h
new file mode 100644
index 0000000..b20343d
--- /dev/null
+++ b/src/aosp/suggest/core/suggest.h
@@ -0,0 +1,94 @@
+/*
+ * 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_SUGGEST_IMPL_H
+#define LATINIME_SUGGEST_IMPL_H
+
+#include "defines.h"
+#include "suggest/core/suggest_interface.h"
+#include "suggest/core/policy/suggest_policy.h"
+
+namespace latinime {
+
+// Naming convention
+// - Distance: "Weighted" edit distance -- used both for spatial and language.
+// - Compound Distance: Spatial Distance + Language Distance -- used for pruning and scoring
+// - Cost: delta/diff for Distance -- used both for spatial and language
+// - Length: "Non-weighted" -- used only for spatial
+// - Probability: "Non-weighted" -- used only for language
+// - Score: Final calibrated score based on the compound distance, which is sent to java as the
+//       priority of a suggested word
+
+class DicNode;
+class DicTraverseSession;
+class ProximityInfo;
+class Scoring;
+class Traversal;
+class Weighting;
+
+class Suggest : public SuggestInterface {
+ public:
+    AK_FORCE_INLINE Suggest(const SuggestPolicy *const suggestPolicy)
+            : TRAVERSAL(suggestPolicy ? suggestPolicy->getTraversal() : 0),
+              SCORING(suggestPolicy ? suggestPolicy->getScoring() : 0),
+              WEIGHTING(suggestPolicy ? suggestPolicy->getWeighting() : 0) {}
+    AK_FORCE_INLINE virtual ~Suggest() {}
+    int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs,
+            int *times, int *pointerIds, int *inputCodePoints, int inputSize, int commitPoint,
+            int *outWords, int *frequencies, int *outputIndices, int *outputTypes,
+            int *outputAutoCommitFirstWordConfidence) const;
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Suggest);
+    void createNextWordDicNode(DicTraverseSession *traverseSession, DicNode *dicNode,
+            const bool spaceSubstitution) const;
+    int outputSuggestions(DicTraverseSession *traverseSession, int *frequencies,
+            int *outputCodePoints, int *outputIndicesToPartialCommit, int *outputTypes,
+            int *outputAutoCommitFirstWordConfidence) const;
+    int computeFirstWordConfidence(const DicNode *const terminalDicNode) const;
+    void initializeSearch(DicTraverseSession *traverseSession, int commitPoint) const;
+    void expandCurrentDicNodes(DicTraverseSession *traverseSession) const;
+    void processTerminalDicNode(DicTraverseSession *traverseSession, DicNode *dicNode) const;
+    void processExpandedDicNode(DicTraverseSession *traverseSession, DicNode *dicNode) const;
+    void weightChildNode(DicTraverseSession *traverseSession, DicNode *dicNode) const;
+    float getAutocorrectScore(DicTraverseSession *traverseSession, DicNode *dicNode) const;
+    void generateFeatures(
+            DicTraverseSession *traverseSession, DicNode *dicNode, float *features) const;
+    void processDicNodeAsOmission(DicTraverseSession *traverseSession, DicNode *dicNode) const;
+    void processDicNodeAsDigraph(DicTraverseSession *traverseSession, DicNode *dicNode) const;
+    void processDicNodeAsTransposition(DicTraverseSession *traverseSession,
+            DicNode *dicNode) const;
+    void processDicNodeAsInsertion(DicTraverseSession *traverseSession, DicNode *dicNode) const;
+    void processDicNodeAsAdditionalProximityChar(DicTraverseSession *traverseSession,
+            DicNode *dicNode, DicNode *childDicNode) const;
+    void processDicNodeAsSubstitution(DicTraverseSession *traverseSession, DicNode *dicNode,
+            DicNode *childDicNode) const;
+    void processDicNodeAsMatch(DicTraverseSession *traverseSession,
+            DicNode *childDicNode) const;
+
+    // Inputs longer than this will autocorrect if the suggestion is multi-word
+    static const int MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT;
+    static const int MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE;
+
+    // Threshold for autocorrection classifier
+    static const float AUTOCORRECT_CLASSIFICATION_THRESHOLD;
+
+    const Traversal *const TRAVERSAL;
+    const Scoring *const SCORING;
+    const Weighting *const WEIGHTING;
+};
+} // namespace latinime
+#endif // LATINIME_SUGGEST_IMPL_H
diff --git a/src/aosp/suggest/core/suggest_interface.h b/src/aosp/suggest/core/suggest_interface.h
new file mode 100644
index 0000000..4deb4d9
--- /dev/null
+++ b/src/aosp/suggest/core/suggest_interface.h
@@ -0,0 +1,38 @@
+/*
+ * 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_SUGGEST_INTERFACE_H
+#define LATINIME_SUGGEST_INTERFACE_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class ProximityInfo;
+
+class SuggestInterface {
+ public:
+    virtual int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs,
+            int *inputYs, int *times, int *pointerIds, int *inputCodePoints, int inputSize,
+            int commitPoint, int *outWords, int *frequencies, int *outputIndices,
+            int *outputTypes, int *outputAutoCommitFirstWordConfidence) const = 0;
+    SuggestInterface() {}
+    virtual ~SuggestInterface() {}
+ private:
+    DISALLOW_COPY_AND_ASSIGN(SuggestInterface);
+};
+} // namespace latinime
+#endif // LATINIME_SUGGEST_INTERFACE_H
diff --git a/src/aosp/suggest/core/suggest_options.h b/src/aosp/suggest/core/suggest_options.h
new file mode 100644
index 0000000..1b21aaf
--- /dev/null
+++ b/src/aosp/suggest/core/suggest_options.h
@@ -0,0 +1,74 @@
+/*
+ * 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_SUGGEST_OPTIONS_H
+#define LATINIME_SUGGEST_OPTIONS_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class SuggestOptions{
+ public:
+    SuggestOptions(const int *const options, const int length)
+            : mOptions(options), mLength(length) {}
+
+    AK_FORCE_INLINE bool isGesture() const {
+        return getBoolOption(IS_GESTURE);
+    }
+
+    AK_FORCE_INLINE bool useFullEditDistance() const {
+        return getBoolOption(USE_FULL_EDIT_DISTANCE);
+    }
+
+    AK_FORCE_INLINE bool getAdditionalFeaturesBoolOption(const int key) const {
+        return getBoolOption(key + ADDITIONAL_FEATURES_OPTIONS);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(SuggestOptions);
+
+    // Need to update com.android.inputmethod.latin.NativeSuggestOptions when you add, remove or
+    // reorder options.
+    static const int IS_GESTURE = 0;
+    static const int USE_FULL_EDIT_DISTANCE = 1;
+    // Additional features options are stored after the other options and used as setting values of
+    // experimental features.
+    static const int ADDITIONAL_FEATURES_OPTIONS = 2;
+
+    const int *const mOptions;
+    const int mLength;
+
+    AK_FORCE_INLINE bool isValidKey(const int key) const {
+        return 0 <= key && key < mLength;
+    }
+
+    AK_FORCE_INLINE bool getBoolOption(const int key) const {
+        if (isValidKey(key)) {
+            return mOptions[key] != 0;
+        }
+        return false;
+    }
+
+    AK_FORCE_INLINE int getIntOption(const int key) const {
+        if (isValidKey(key)) {
+            return mOptions[key];
+        }
+        return 0;
+    }
+};
+} // namespace latinime
+#endif // LATINIME_SUGGEST_OPTIONS_H
diff --git a/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h b/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h
new file mode 100644
index 0000000..6ff95ca
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_policy.h
@@ -0,0 +1,53 @@
+/*
+ * 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_BIGRAM_LIST_POLICY_H
+#define LATINIME_BIGRAM_LIST_POLICY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+
+namespace latinime {
+
+class BigramListPolicy : public DictionaryBigramsStructurePolicy {
+ public:
+    explicit BigramListPolicy(const uint8_t *const bigramsBuf) : mBigramsBuf(bigramsBuf) {}
+
+    ~BigramListPolicy() {}
+
+    void getNextBigram(int *const outBigramPos, int *const outProbability, bool *const outHasNext,
+            int *const pos) const {
+        BigramListReadWriteUtils::BigramFlags flags;
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(mBigramsBuf, &flags,
+                outBigramPos, pos);
+        *outProbability = BigramListReadWriteUtils::getProbabilityFromFlags(flags);
+        *outHasNext = BigramListReadWriteUtils::hasNext(flags);
+    }
+
+    void skipAllBigrams(int *const pos) const {
+        BigramListReadWriteUtils::skipExistingBigrams(mBigramsBuf, pos);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BigramListPolicy);
+
+    const uint8_t *const mBigramsBuf;
+};
+} // namespace latinime
+#endif // LATINIME_BIGRAM_LIST_POLICY_H
diff --git a/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp b/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
new file mode 100644
index 0000000..1926b98
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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/bigram/bigram_list_read_write_utils.h"
+
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/byte_array_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const BigramListReadWriteUtils::BigramFlags BigramListReadWriteUtils::MASK_ATTRIBUTE_ADDRESS_TYPE =
+        0x30;
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
+// Flag for presence of more attributes
+const BigramListReadWriteUtils::BigramFlags BigramListReadWriteUtils::FLAG_ATTRIBUTE_HAS_NEXT =
+        0x80;
+// Mask for attribute probability, stored on 4 bits inside the flags byte.
+const BigramListReadWriteUtils::BigramFlags
+        BigramListReadWriteUtils::MASK_ATTRIBUTE_PROBABILITY = 0x0F;
+const int BigramListReadWriteUtils::ATTRIBUTE_ADDRESS_SHIFT = 4;
+
+/* static */ void BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+        const uint8_t *const bigramsBuf, BigramFlags *const outBigramFlags,
+        int *const outTargetPtNodePos, int *const bigramEntryPos) {
+    const BigramFlags bigramFlags = ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf,
+            bigramEntryPos);
+    if (outBigramFlags) {
+        *outBigramFlags = bigramFlags;
+    }
+    const int targetPos = getBigramAddressAndAdvancePosition(bigramsBuf, bigramFlags,
+            bigramEntryPos);
+    if (outTargetPtNodePos) {
+        *outTargetPtNodePos = targetPos;
+    }
+}
+
+/* static */ void BigramListReadWriteUtils::skipExistingBigrams(const uint8_t *const bigramsBuf,
+        int *const bigramListPos) {
+    BigramFlags flags;
+    do {
+        getBigramEntryPropertiesAndAdvancePosition(bigramsBuf, &flags, 0 /* outTargetPtNodePos */,
+                bigramListPos);
+    } while(hasNext(flags));
+}
+
+/* static */ int BigramListReadWriteUtils::getBigramAddressAndAdvancePosition(
+        const uint8_t *const bigramsBuf, const BigramFlags flags, int *const pos) {
+    int offset = 0;
+    const int origin = *pos;
+    switch (MASK_ATTRIBUTE_ADDRESS_TYPE & flags) {
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
+            offset = ByteArrayUtils::readUint8AndAdvancePosition(bigramsBuf, pos);
+            break;
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
+            offset = ByteArrayUtils::readUint16AndAdvancePosition(bigramsBuf, pos);
+            break;
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
+            offset = ByteArrayUtils::readUint24AndAdvancePosition(bigramsBuf, pos);
+            break;
+    }
+    if (offset == DynamicPatriciaTrieReadingUtils::DICT_OFFSET_INVALID) {
+        return NOT_A_DICT_POS;
+    } else if (offset == DynamicPatriciaTrieReadingUtils::DICT_OFFSET_ZERO_OFFSET) {
+        return origin;
+    }
+    if (isOffsetNegative(flags)) {
+        return origin - offset;
+    } else {
+        return origin + offset;
+    }
+}
+
+/* static */ bool BigramListReadWriteUtils::setHasNextFlag(
+        BufferWithExtendableBuffer *const buffer, const bool hasNext, const int entryPos) {
+    const bool usesAdditionalBuffer = buffer->isInAdditionalBuffer(entryPos);
+    int readingPos = entryPos;
+    if (usesAdditionalBuffer) {
+        readingPos -= buffer->getOriginalBufferSize();
+    }
+    BigramFlags bigramFlags = ByteArrayUtils::readUint8AndAdvancePosition(
+            buffer->getBuffer(usesAdditionalBuffer), &readingPos);
+    if (hasNext) {
+        bigramFlags = bigramFlags | FLAG_ATTRIBUTE_HAS_NEXT;
+    } else {
+        bigramFlags = bigramFlags & (~FLAG_ATTRIBUTE_HAS_NEXT);
+    }
+    int writingPos = entryPos;
+    return buffer->writeUintAndAdvancePosition(bigramFlags, 1 /* size */, &writingPos);
+}
+
+/* static */ bool BigramListReadWriteUtils::createAndWriteBigramEntry(
+        BufferWithExtendableBuffer *const buffer, const int targetPos, const int probability,
+        const bool hasNext, int *const writingPos) {
+    BigramFlags flags;
+    if (!createAndGetBigramFlags(*writingPos, targetPos, probability, hasNext, &flags)) {
+        return false;
+    }
+    return writeBigramEntry(buffer, flags, targetPos, writingPos);
+}
+
+/* static */ bool BigramListReadWriteUtils::writeBigramEntry(
+        BufferWithExtendableBuffer *const bufferToWrite, const BigramFlags flags,
+        const int targetPtNodePos, int *const writingPos) {
+    const int offset = getBigramTargetOffset(targetPtNodePos, *writingPos);
+    const BigramFlags flagsToWrite = (offset < 0) ?
+            (flags | FLAG_ATTRIBUTE_OFFSET_NEGATIVE) : (flags & ~FLAG_ATTRIBUTE_OFFSET_NEGATIVE);
+    if (!bufferToWrite->writeUintAndAdvancePosition(flagsToWrite, 1 /* size */, writingPos)) {
+        return false;
+    }
+    const uint32_t absOffest = abs(offset);
+    const int bigramTargetFieldSize = attributeAddressSize(flags);
+    return bufferToWrite->writeUintAndAdvancePosition(absOffest, bigramTargetFieldSize,
+            writingPos);
+}
+
+// Returns true if the bigram entry is valid and put entry flags into out*.
+/* static */ bool BigramListReadWriteUtils::createAndGetBigramFlags(const int entryPos,
+        const int targetPtNodePos, const int probability, const bool hasNext,
+        BigramFlags *const outBigramFlags) {
+    BigramFlags flags = probability & MASK_ATTRIBUTE_PROBABILITY;
+    if (hasNext) {
+        flags |= FLAG_ATTRIBUTE_HAS_NEXT;
+    }
+    const int offset = getBigramTargetOffset(targetPtNodePos, entryPos);
+    if (offset < 0) {
+        flags |= FLAG_ATTRIBUTE_OFFSET_NEGATIVE;
+    }
+    const uint32_t absOffest = abs(offset);
+    if ((absOffest >> 24) != 0) {
+        // Offset is too large.
+        return false;
+    } else if ((absOffest >> 16) != 0) {
+        flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+    } else if ((absOffest >> 8) != 0) {
+        flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+    } else {
+        flags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+    }
+    // Currently, all newly written bigram position fields are 3 bytes to simplify dictionary
+    // writing.
+    // TODO: Remove following 2 lines and optimize memory space.
+    flags = (flags & (~MASK_ATTRIBUTE_ADDRESS_TYPE)) | FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+    *outBigramFlags = flags;
+    return true;
+}
+
+/* static */ int BigramListReadWriteUtils::getBigramTargetOffset(const int targetPtNodePos,
+        const int entryPos) {
+    if (targetPtNodePos == NOT_A_DICT_POS) {
+        return DynamicPatriciaTrieReadingUtils::DICT_OFFSET_INVALID;
+    } else {
+        const int offset = targetPtNodePos - (entryPos + 1 /* bigramFlagsField */);
+        if (offset == 0) {
+            return DynamicPatriciaTrieReadingUtils::DICT_OFFSET_ZERO_OFFSET;
+        } else {
+            return offset;
+        }
+    }
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h b/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
new file mode 100644
index 0000000..eabe4e0
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h
@@ -0,0 +1,102 @@
+/*
+ * 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_BIGRAM_LIST_READ_WRITE_UTILS_H
+#define LATINIME_BIGRAM_LIST_READ_WRITE_UTILS_H
+
+#include <cstdlib>
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+
+class BigramListReadWriteUtils {
+public:
+   typedef uint8_t BigramFlags;
+
+   static void getBigramEntryPropertiesAndAdvancePosition(const uint8_t *const bigramsBuf,
+           BigramFlags *const outBigramFlags, int *const outTargetPtNodePos,
+           int *const bigramEntryPos);
+
+   static AK_FORCE_INLINE int getProbabilityFromFlags(const BigramFlags flags) {
+       return flags & MASK_ATTRIBUTE_PROBABILITY;
+   }
+
+   static AK_FORCE_INLINE bool hasNext(const BigramFlags flags) {
+       return (flags & FLAG_ATTRIBUTE_HAS_NEXT) != 0;
+   }
+
+   // Bigrams reading methods
+   static void skipExistingBigrams(const uint8_t *const bigramsBuf, int *const bigramListPos);
+
+   // Returns the size of the bigram position field that is stored in bigram flags.
+   static AK_FORCE_INLINE int attributeAddressSize(const BigramFlags flags) {
+       return (flags & MASK_ATTRIBUTE_ADDRESS_TYPE) >> ATTRIBUTE_ADDRESS_SHIFT;
+       /* Note: this is a value-dependant optimization of what may probably be
+          more readably written this way:
+          switch (flags * BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) {
+          case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: return 1;
+          case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: return 2;
+          case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTE: return 3;
+          default: return 0;
+          }
+       */
+   }
+
+   static bool setHasNextFlag(BufferWithExtendableBuffer *const buffer,
+           const bool hasNext, const int entryPos);
+
+   static AK_FORCE_INLINE BigramFlags setProbabilityInFlags(const BigramFlags flags,
+           const int probability) {
+       return (flags & (~MASK_ATTRIBUTE_PROBABILITY)) | (probability & MASK_ATTRIBUTE_PROBABILITY);
+   }
+
+   static bool createAndWriteBigramEntry(BufferWithExtendableBuffer *const buffer,
+           const int targetPos, const int probability, const bool hasNext, int *const writingPos);
+
+   static bool writeBigramEntry(BufferWithExtendableBuffer *const buffer, const BigramFlags flags,
+           const int targetOffset, int *const writingPos);
+
+private:
+   DISALLOW_IMPLICIT_CONSTRUCTORS(BigramListReadWriteUtils);
+
+   static const BigramFlags MASK_ATTRIBUTE_ADDRESS_TYPE;
+   static const BigramFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+   static const BigramFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+   static const BigramFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+   static const BigramFlags FLAG_ATTRIBUTE_OFFSET_NEGATIVE;
+   static const BigramFlags FLAG_ATTRIBUTE_HAS_NEXT;
+   static const BigramFlags MASK_ATTRIBUTE_PROBABILITY;
+   static const int ATTRIBUTE_ADDRESS_SHIFT;
+
+   // Returns true if the bigram entry is valid and put entry flags into out*.
+   static bool createAndGetBigramFlags(const int entryPos, const int targetPos,
+           const int probability, const bool hasNext, BigramFlags *const outBigramFlags);
+
+   static AK_FORCE_INLINE bool isOffsetNegative(const BigramFlags flags) {
+       return (flags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) != 0;
+   }
+
+   static int getBigramAddressAndAdvancePosition(const uint8_t *const bigramsBuf,
+           const BigramFlags flags, int *const pos);
+
+   static int getBigramTargetOffset(const int targetPtNodePos, const int entryPos);
+};
+} // namespace latinime
+#endif // LATINIME_BIGRAM_LIST_READ_WRITE_UTILS_H
diff --git a/src/aosp/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp b/src/aosp/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
new file mode 100644
index 0000000..b1170e2
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.cpp
@@ -0,0 +1,391 @@
+/*
+ * 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/bigram/dynamic_bigram_list_policy.h"
+
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
+
+namespace latinime {
+
+const int DynamicBigramListPolicy::CONTINUING_BIGRAM_LINK_COUNT_LIMIT = 10000;
+const int DynamicBigramListPolicy::BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT = 100000;
+
+void DynamicBigramListPolicy::getNextBigram(int *const outBigramPos, int *const outProbability,
+        bool *const outHasNext, int *const bigramEntryPos) const {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramEntryPos);
+    const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        *bigramEntryPos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int originalBigramPos;
+    BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(buffer, &bigramFlags,
+            &originalBigramPos, bigramEntryPos);
+    if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
+        originalBigramPos += mBuffer->getOriginalBufferSize();
+    }
+    *outProbability = BigramListReadWriteUtils::getProbabilityFromFlags(bigramFlags);
+    *outHasNext = BigramListReadWriteUtils::hasNext(bigramFlags);
+    if (mIsDecayingDict && !ForgettingCurveUtils::isValidEncodedProbability(*outProbability)) {
+        // This bigram is too weak to output.
+        *outBigramPos = NOT_A_DICT_POS;
+    } else {
+        *outBigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
+    }
+    if (usesAdditionalBuffer) {
+        *bigramEntryPos += mBuffer->getOriginalBufferSize();
+    }
+}
+
+void DynamicBigramListPolicy::skipAllBigrams(int *const bigramListPos) const {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
+    const uint8_t *const buffer = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::skipExistingBigrams(buffer, bigramListPos);
+    if (usesAdditionalBuffer) {
+        *bigramListPos += mBuffer->getOriginalBufferSize();
+    }
+}
+
+bool DynamicBigramListPolicy::copyAllBigrams(BufferWithExtendableBuffer *const bufferToWrite,
+        int *const fromPos, int *const toPos, int *const outBigramsCount) const {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*fromPos);
+    if (usesAdditionalBuffer) {
+        *fromPos -= mBuffer->getOriginalBufferSize();
+    }
+    *outBigramsCount = 0;
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    int lastWrittenEntryPos = NOT_A_DICT_POS;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        // The buffer address can be changed after calling buffer writing methods.
+        int originalBigramPos;
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos,
+                fromPos);
+        if (originalBigramPos == NOT_A_DICT_POS) {
+            // skip invalid bigram entry.
+            continue;
+        }
+        if (usesAdditionalBuffer) {
+            originalBigramPos += mBuffer->getOriginalBufferSize();
+        }
+        const int bigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
+        if (bigramPos == NOT_A_DICT_POS) {
+            // Target PtNode has been invalidated.
+            continue;
+        }
+        lastWrittenEntryPos = *toPos;
+        if (!BigramListReadWriteUtils::createAndWriteBigramEntry(bufferToWrite, bigramPos,
+                BigramListReadWriteUtils::getProbabilityFromFlags(bigramFlags),
+                BigramListReadWriteUtils::hasNext(bigramFlags), toPos)) {
+            return false;
+        }
+        (*outBigramsCount)++;
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    // Makes the last entry the terminal of the list. Updates the flags.
+    if (lastWrittenEntryPos != NOT_A_DICT_POS) {
+        if (!BigramListReadWriteUtils::setHasNextFlag(bufferToWrite, false /* hasNext */,
+                lastWrittenEntryPos)) {
+            return false;
+        }
+    }
+    if (usesAdditionalBuffer) {
+        *fromPos += mBuffer->getOriginalBufferSize();
+    }
+    return true;
+}
+
+// Finding useless bigram entries and remove them. Bigram entry is useless when the target PtNode
+// has been deleted or is not a valid terminal.
+bool DynamicBigramListPolicy::updateAllBigramEntriesAndDeleteUselessEntries(
+        int *const bigramListPos, int *const outValidBigramEntryCount) {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
+    if (usesAdditionalBuffer) {
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
+    }
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, this /* bigramsPolicy */, mShortcutPolicy);
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        int bigramEntryPos = *bigramListPos;
+        int originalBigramPos;
+        // The buffer address can be changed after calling buffer writing methods.
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos,
+                bigramListPos);
+        if (usesAdditionalBuffer) {
+            bigramEntryPos += mBuffer->getOriginalBufferSize();
+        }
+        if (originalBigramPos == NOT_A_DICT_POS) {
+            // This entry has already been removed.
+            continue;
+        }
+        if (usesAdditionalBuffer) {
+            originalBigramPos += mBuffer->getOriginalBufferSize();
+        }
+        const int bigramTargetNodePos =
+                followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
+        nodeReader.fetchNodeInfoInBufferFromPtNodePos(bigramTargetNodePos);
+        if (nodeReader.isDeleted() || !nodeReader.isTerminal()
+                || bigramTargetNodePos == NOT_A_DICT_POS) {
+            // The target is no longer valid terminal. Invalidate the current bigram entry.
+            if (!BigramListReadWriteUtils::writeBigramEntry(mBuffer, bigramFlags,
+                    NOT_A_DICT_POS /* targetPtNodePos */, &bigramEntryPos)) {
+                return false;
+            }
+            continue;
+        }
+        bool isRemoved = false;
+        if (!updateProbabilityForDecay(bigramFlags, bigramTargetNodePos, &bigramEntryPos,
+                &isRemoved)) {
+            return false;
+        }
+        if (!isRemoved) {
+            (*outValidBigramEntryCount) += 1;
+        }
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    return true;
+}
+
+// Updates bigram target PtNode positions in the list after the placing step in GC.
+bool DynamicBigramListPolicy::updateAllBigramTargetPtNodePositions(int *const bigramListPos,
+        const DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap *const
+                ptNodePositionRelocationMap, int *const outBigramEntryCount) {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
+    if (usesAdditionalBuffer) {
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        int bigramEntryPos = *bigramListPos;
+        if (usesAdditionalBuffer) {
+            bigramEntryPos += mBuffer->getOriginalBufferSize();
+        }
+        int bigramTargetPtNodePos;
+        // The buffer address can be changed after calling buffer writing methods.
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &bigramTargetPtNodePos,
+                bigramListPos);
+        if (bigramTargetPtNodePos == NOT_A_DICT_POS) {
+            continue;
+        }
+        if (usesAdditionalBuffer) {
+            bigramTargetPtNodePos += mBuffer->getOriginalBufferSize();
+        }
+
+        DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::const_iterator it =
+                ptNodePositionRelocationMap->find(bigramTargetPtNodePos);
+        if (it != ptNodePositionRelocationMap->end()) {
+            bigramTargetPtNodePos = it->second;
+        } else {
+            bigramTargetPtNodePos = NOT_A_DICT_POS;
+        }
+        if (!BigramListReadWriteUtils::writeBigramEntry(mBuffer, bigramFlags,
+                bigramTargetPtNodePos, &bigramEntryPos)) {
+            return false;
+        }
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    (*outBigramEntryCount) = bigramEntryCount;
+    return true;
+}
+
+bool DynamicBigramListPolicy::addNewBigramEntryToBigramList(const int bigramTargetPos,
+        const int probability, int *const bigramListPos, bool *const outAddedNewBigram) {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(*bigramListPos);
+    if (usesAdditionalBuffer) {
+        *bigramListPos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        int entryPos = *bigramListPos;
+        if (usesAdditionalBuffer) {
+            entryPos += mBuffer->getOriginalBufferSize();
+        }
+        int originalBigramPos;
+        // The buffer address can be changed after calling buffer writing methods.
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos,
+                bigramListPos);
+        if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
+            originalBigramPos += mBuffer->getOriginalBufferSize();
+        }
+        if (followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos) == bigramTargetPos) {
+            // Update this bigram entry.
+            *outAddedNewBigram = false;
+            const int originalProbability = BigramListReadWriteUtils::getProbabilityFromFlags(
+                    bigramFlags);
+            const int probabilityToWrite = mIsDecayingDict ?
+                    ForgettingCurveUtils::getUpdatedEncodedProbability(originalProbability,
+                            probability) : probability;
+            const BigramListReadWriteUtils::BigramFlags updatedFlags =
+                    BigramListReadWriteUtils::setProbabilityInFlags(bigramFlags,
+                            probabilityToWrite);
+            return BigramListReadWriteUtils::writeBigramEntry(mBuffer, updatedFlags,
+                    originalBigramPos, &entryPos);
+        }
+        if (BigramListReadWriteUtils::hasNext(bigramFlags)) {
+            continue;
+        }
+        // The current last entry is found.
+        // First, update the flags of the last entry.
+        if (!BigramListReadWriteUtils::setHasNextFlag(mBuffer, true /* hasNext */, entryPos)) {
+            *outAddedNewBigram = false;
+            return false;
+        }
+        if (usesAdditionalBuffer) {
+            *bigramListPos += mBuffer->getOriginalBufferSize();
+        }
+        // Then, add a new entry after the last entry.
+        *outAddedNewBigram = true;
+        return writeNewBigramEntry(bigramTargetPos, probability, bigramListPos);
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    // We return directly from the while loop.
+    ASSERT(false);
+    return false;
+}
+
+bool DynamicBigramListPolicy::writeNewBigramEntry(const int bigramTargetPos, const int probability,
+        int *const writingPos) {
+    // hasNext is false because we are adding a new bigram entry at the end of the bigram list.
+    const int probabilityToWrite = mIsDecayingDict ?
+            ForgettingCurveUtils::getUpdatedEncodedProbability(NOT_A_PROBABILITY, probability) :
+                    probability;
+    return BigramListReadWriteUtils::createAndWriteBigramEntry(mBuffer, bigramTargetPos,
+            probabilityToWrite, false /* hasNext */, writingPos);
+}
+
+bool DynamicBigramListPolicy::removeBigram(const int bigramListPos, const int bigramTargetPos) {
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(bigramListPos);
+    int pos = bigramListPos;
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    BigramListReadWriteUtils::BigramFlags bigramFlags;
+    int bigramEntryCount = 0;
+    do {
+        if (++bigramEntryCount > BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT) {
+            AKLOGE("Too many bigram entries. Entry count: %d, Limit: %d",
+                    bigramEntryCount, BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT);
+            ASSERT(false);
+            return false;
+        }
+        int bigramEntryPos = pos;
+        int originalBigramPos;
+        // The buffer address can be changed after calling buffer writing methods.
+        BigramListReadWriteUtils::getBigramEntryPropertiesAndAdvancePosition(
+                mBuffer->getBuffer(usesAdditionalBuffer), &bigramFlags, &originalBigramPos, &pos);
+        if (usesAdditionalBuffer) {
+            bigramEntryPos += mBuffer->getOriginalBufferSize();
+        }
+        if (usesAdditionalBuffer && originalBigramPos != NOT_A_DICT_POS) {
+            originalBigramPos += mBuffer->getOriginalBufferSize();
+        }
+        const int bigramPos = followBigramLinkAndGetCurrentBigramPtNodePos(originalBigramPos);
+        if (bigramPos != bigramTargetPos) {
+            continue;
+        }
+        // Target entry is found. Write an invalid target position to mark the bigram invalid.
+        return BigramListReadWriteUtils::writeBigramEntry(mBuffer, bigramFlags,
+                NOT_A_DICT_POS /* targetOffset */, &bigramEntryPos);
+    } while(BigramListReadWriteUtils::hasNext(bigramFlags));
+    return false;
+}
+
+int DynamicBigramListPolicy::followBigramLinkAndGetCurrentBigramPtNodePos(
+        const int originalBigramPos) const {
+    if (originalBigramPos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
+    }
+    int currentPos = originalBigramPos;
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, this /* bigramsPolicy */, mShortcutPolicy);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos);
+    int bigramLinkCount = 0;
+    while (nodeReader.getBigramLinkedNodePos() != NOT_A_DICT_POS) {
+        currentPos = nodeReader.getBigramLinkedNodePos();
+        nodeReader.fetchNodeInfoInBufferFromPtNodePos(currentPos);
+        bigramLinkCount++;
+        if (bigramLinkCount > CONTINUING_BIGRAM_LINK_COUNT_LIMIT) {
+            AKLOGE("Bigram link is invalid. start position: %d", originalBigramPos);
+            ASSERT(false);
+            return NOT_A_DICT_POS;
+        }
+    }
+    return currentPos;
+}
+
+bool DynamicBigramListPolicy::updateProbabilityForDecay(
+        const BigramListReadWriteUtils::BigramFlags bigramFlags, const int targetPtNodePos,
+        int *const bigramEntryPos, bool *const outRemoved) const {
+    *outRemoved = false;
+    if (mIsDecayingDict) {
+        // Update bigram probability for decaying.
+        const int newProbability = ForgettingCurveUtils::getEncodedProbabilityToSave(
+                BigramListReadWriteUtils::getProbabilityFromFlags(bigramFlags), mHeaderPolicy);
+        if (ForgettingCurveUtils::isValidEncodedProbability(newProbability)) {
+            // Write new probability.
+            const BigramListReadWriteUtils::BigramFlags updatedBigramFlags =
+                    BigramListReadWriteUtils::setProbabilityInFlags(
+                            bigramFlags, newProbability);
+            if (!BigramListReadWriteUtils::writeBigramEntry(mBuffer, updatedBigramFlags,
+                    targetPtNodePos, bigramEntryPos)) {
+                return false;
+            }
+        } else {
+            // Remove current bigram entry.
+            *outRemoved = true;
+            if (!BigramListReadWriteUtils::writeBigramEntry(mBuffer, bigramFlags,
+                    NOT_A_DICT_POS /* targetPtNodePos */, bigramEntryPos)) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h b/src/aosp/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
new file mode 100644
index 0000000..0504b59
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h
@@ -0,0 +1,92 @@
+/*
+ * 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_BIGRAM_LIST_POLICY_H
+#define LATINIME_DYNAMIC_BIGRAM_LIST_POLICY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/bigram_list_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DictionaryHeaderStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This is a dynamic version of BigramListPolicy and supports an additional buffer.
+ */
+class DynamicBigramListPolicy : public DictionaryBigramsStructurePolicy {
+ public:
+    DynamicBigramListPolicy(const DictionaryHeaderStructurePolicy *const headerPolicy,
+            BufferWithExtendableBuffer *const buffer,
+            const DictionaryShortcutsStructurePolicy *const shortcutPolicy,
+            const bool isDecayingDict)
+            : mHeaderPolicy(headerPolicy), mBuffer(buffer), mShortcutPolicy(shortcutPolicy),
+              mIsDecayingDict(isDecayingDict) {}
+
+    ~DynamicBigramListPolicy() {}
+
+    void getNextBigram(int *const outBigramPos, int *const outProbability, bool *const outHasNext,
+            int *const bigramEntryPos) const;
+
+    void skipAllBigrams(int *const bigramListPos) const;
+
+    // Copy bigrams from the bigram list that starts at fromPos in mBuffer to toPos in
+    // bufferToWrite and advance these positions after bigram lists. This method skips invalid
+    // bigram entries and write the valid bigram entry count to outBigramsCount.
+    bool copyAllBigrams(BufferWithExtendableBuffer *const bufferToWrite, int *const fromPos,
+            int *const toPos, int *const outBigramsCount) const;
+
+    bool updateAllBigramEntriesAndDeleteUselessEntries(int *const bigramListPos,
+            int *const outBigramEntryCount);
+
+    bool updateAllBigramTargetPtNodePositions(int *const bigramListPos,
+            const DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap *const
+                    ptNodePositionRelocationMap, int *const outValidBigramEntryCount);
+
+    bool addNewBigramEntryToBigramList(const int bigramTargetPos, const int probability,
+            int *const bigramListPos, bool *const outAddedNewBigram);
+
+    bool writeNewBigramEntry(const int bigramTargetPos, const int probability,
+            int *const writingPos);
+
+    // Return whether or not targetBigramPos is found.
+    bool removeBigram(const int bigramListPos, const int bigramTargetPos);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicBigramListPolicy);
+
+    static const int CONTINUING_BIGRAM_LINK_COUNT_LIMIT;
+    static const int BIGRAM_ENTRY_COUNT_IN_A_BIGRAM_LIST_LIMIT;
+
+    const DictionaryHeaderStructurePolicy *const mHeaderPolicy;
+    BufferWithExtendableBuffer *const mBuffer;
+    const DictionaryShortcutsStructurePolicy *const mShortcutPolicy;
+    const bool mIsDecayingDict;
+
+    // Follow bigram link and return the position of bigram target PtNode that is currently valid.
+    int followBigramLinkAndGetCurrentBigramPtNodePos(const int originalBigramPos) const;
+
+    bool updateProbabilityForDecay(const BigramListReadWriteUtils::BigramFlags bigramFlags,
+            const int targetPtNodePos, int *const bigramEntryPos, bool *const outRemoved) const;
+};
+} // namespace latinime
+#endif // LATINIME_DYNAMIC_BIGRAM_LIST_POLICY_H
diff --git a/src/aosp/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp b/src/aosp/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp
new file mode 100644
index 0000000..ff80dd2
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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/dictionary_structure_with_buffer_policy_factory.h"
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
+
+namespace latinime {
+
+/* static */ DictionaryStructureWithBufferPolicy *DictionaryStructureWithBufferPolicyFactory
+        ::newDictionaryStructureWithBufferPolicy(const char *const path, const int bufOffset,
+                const int size, const bool isUpdatable) {
+    // Allocated buffer in MmapedBuffer::openBuffer() will be freed in the destructor of
+    // impl classes of DictionaryStructureWithBufferPolicy.
+    const MmappedBuffer *const mmapedBuffer = MmappedBuffer::openBuffer(path, bufOffset, size,
+            isUpdatable);
+    if (!mmapedBuffer) {
+        return 0;
+    }
+    switch (FormatUtils::detectFormatVersion(mmapedBuffer->getBuffer(),
+            mmapedBuffer->getBufferSize())) {
+        case FormatUtils::VERSION_2:
+            return new PatriciaTriePolicy(mmapedBuffer);
+        case FormatUtils::VERSION_3:
+            return new DynamicPatriciaTriePolicy(mmapedBuffer);
+        default:
+            AKLOGE("DICT: dictionary format is unknown, bad magic number");
+            delete mmapedBuffer;
+            ASSERT(false);
+            return 0;
+    }
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h b/src/aosp/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h
new file mode 100644
index 0000000..8cebc3b
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dictionary_structure_with_buffer_policy_factory.h
@@ -0,0 +1,36 @@
+/*
+ * 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_DICTIONARY_STRUCTURE_WITH_BUFFER_POLICY_FACTORY_H
+#define LATINIME_DICTIONARY_STRUCTURE_WITH_BUFFER_POLICY_FACTORY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+
+namespace latinime {
+
+class DictionaryStructureWithBufferPolicyFactory {
+ public:
+    static DictionaryStructureWithBufferPolicy *newDictionaryStructureWithBufferPolicy(
+            const char *const path, const int bufOffset, const int size, const bool isUpdatable);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DictionaryStructureWithBufferPolicyFactory);
+};
+} // namespace latinime
+#endif // LATINIME_DICTIONARY_STRUCTURE_WITH_BUFFER_POLICY_FACTORY_H
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
new file mode 100644
index 0000000..5724c5d
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.cpp
@@ -0,0 +1,191 @@
+/*
+ * 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_gc_event_listeners.h"
+
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
+
+namespace latinime {
+
+bool DynamicPatriciaTrieGcEventListeners
+        ::TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+                ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                        const int *const nodeCodePoints) {
+    // PtNode is useless when the PtNode is not a terminal and doesn't have any not useless
+    // children.
+    bool isUselessPtNode = !node->isTerminal();
+    if (node->isTerminal() && mIsDecayingDict) {
+        const int newProbability =
+                ForgettingCurveUtils::getEncodedProbabilityToSave(node->getProbability(),
+                        mHeaderPolicy);
+        int writingPos = node->getProbabilityFieldPos();
+        // Update probability.
+        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(
+                mBuffer, newProbability, &writingPos)) {
+            return false;
+        }
+        if (!ForgettingCurveUtils::isValidEncodedProbability(newProbability)) {
+            isUselessPtNode = true;
+        }
+    }
+    if (mChildrenValue > 0) {
+        isUselessPtNode = false;
+    } else if (node->isTerminal()) {
+        // Remove children as all children are useless.
+        int writingPos = node->getChildrenPosFieldPos();
+        if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
+                mBuffer, NOT_A_DICT_POS /* childrenPosition */, &writingPos)) {
+            return false;
+        }
+    }
+    if (isUselessPtNode) {
+        // Current PtNode is no longer needed. Mark it as deleted.
+        if (!mWritingHelper->markNodeAsDeleted(node)) {
+            return false;
+        }
+    } else {
+        mValueStack.back() += 1;
+        if (node->isTerminal()) {
+            mValidUnigramCount += 1;
+        }
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateBigramProbability
+        ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints) {
+    if (!node->isDeleted()) {
+        int pos = node->getBigramsPos();
+        if (pos != NOT_A_DICT_POS) {
+            int bigramEntryCount = 0;
+            if (!mBigramPolicy->updateAllBigramEntriesAndDeleteUselessEntries(&pos,
+                    &bigramEntryCount)) {
+                return false;
+            }
+            mValidBigramEntryCount += bigramEntryCount;
+        }
+    }
+    return true;
+}
+
+// Writes dummy PtNode array size when the head of PtNode array is read.
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+        ::onDescend(const int ptNodeArrayPos) {
+    mValidPtNodeCount = 0;
+    int writingPos = mBufferToWrite->getTailPosition();
+    mDictPositionRelocationMap->mPtNodeArrayPositionRelocationMap.insert(
+            DynamicPatriciaTrieWritingHelper::PtNodeArrayPositionRelocationMap::value_type(
+                    ptNodeArrayPos, writingPos));
+    // Writes dummy PtNode array size because arrays can have a forward link or needles PtNodes.
+    // This field will be updated later in onReadingPtNodeArrayTail() with actual PtNode count.
+    mPtNodeArraySizeFieldPos = writingPos;
+    return DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(
+            mBufferToWrite, 0 /* arraySize */, &writingPos);
+}
+
+// Write PtNode array terminal and actual PtNode array size.
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+        ::onReadingPtNodeArrayTail() {
+    int writingPos = mBufferToWrite->getTailPosition();
+    // Write PtNode array terminal.
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(
+            mBufferToWrite, NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    // Write actual PtNode array size.
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(
+            mBufferToWrite, mValidPtNodeCount, &mPtNodeArraySizeFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
+// Write valid PtNode to buffer and memorize mapping from the old position to the new position.
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+        ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints) {
+    if (node->isDeleted()) {
+        // Current PtNode is not written in new buffer because it has been deleted.
+        mDictPositionRelocationMap->mPtNodePositionRelocationMap.insert(
+                DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::value_type(
+                        node->getHeadPos(), NOT_A_DICT_POS));
+        return true;
+    }
+    int writingPos = mBufferToWrite->getTailPosition();
+    mDictPositionRelocationMap->mPtNodePositionRelocationMap.insert(
+            DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::value_type(
+                    node->getHeadPos(), writingPos));
+    mValidPtNodeCount++;
+    // Writes current PtNode.
+    return mWritingHelper->writePtNodeToBufferByCopyingPtNodeInfo(mBufferToWrite, node,
+            node->getParentPos(), nodeCodePoints, node->getCodePointCount(),
+            node->getProbability(), &writingPos);
+}
+
+bool DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateAllPositionFields
+        ::onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints) {
+    // Updates parent position.
+    int parentPos = node->getParentPos();
+    if (parentPos != NOT_A_DICT_POS) {
+        DynamicPatriciaTrieWritingHelper::PtNodePositionRelocationMap::const_iterator it =
+                mDictPositionRelocationMap->mPtNodePositionRelocationMap.find(parentPos);
+        if (it != mDictPositionRelocationMap->mPtNodePositionRelocationMap.end()) {
+            parentPos = it->second;
+        }
+    }
+    int writingPos = node->getHeadPos() + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
+    // Write updated parent offset.
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(mBufferToWrite,
+            parentPos, node->getHeadPos(), &writingPos)) {
+        return false;
+    }
+
+    // Updates children position.
+    int childrenPos = node->getChildrenPos();
+    if (childrenPos != NOT_A_DICT_POS) {
+        DynamicPatriciaTrieWritingHelper::PtNodeArrayPositionRelocationMap::const_iterator it =
+                mDictPositionRelocationMap->mPtNodeArrayPositionRelocationMap.find(childrenPos);
+        if (it != mDictPositionRelocationMap->mPtNodeArrayPositionRelocationMap.end()) {
+            childrenPos = it->second;
+        }
+    }
+    writingPos = node->getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBufferToWrite,
+            childrenPos, &writingPos)) {
+        return false;
+    }
+
+    // Updates bigram target PtNode positions in the bigram list.
+    int bigramsPos = node->getBigramsPos();
+    if (bigramsPos != NOT_A_DICT_POS) {
+        int bigramEntryCount;
+        if (!mBigramPolicy->updateAllBigramTargetPtNodePositions(&bigramsPos,
+                &mDictPositionRelocationMap->mPtNodePositionRelocationMap, &bigramEntryCount)) {
+            return false;
+        }
+        mBigramCount += bigramEntryCount;
+    }
+    if (node->isTerminal()) {
+        mUnigramCount++;
+    }
+
+    return true;
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
new file mode 100644
index 0000000..9755120
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h
@@ -0,0 +1,197 @@
+/*
+ * 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_GC_EVENT_LISTENERS_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_GC_EVENT_LISTENERS_H
+
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+class DictionaryHeaderStructurePolicy;
+
+class DynamicPatriciaTrieGcEventListeners {
+ public:
+    // Updates all PtNodes that can be reached from the root. Checks if each PtNode is useless or
+    // not and marks useless PtNodes as deleted. Such deleted PtNodes will be discarded in the GC.
+    // TODO: Concatenate non-terminal PtNodes.
+    class TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+        : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
+     public:
+        TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted(
+                const DictionaryHeaderStructurePolicy *const headerPolicy,
+                DynamicPatriciaTrieWritingHelper *const writingHelper,
+                BufferWithExtendableBuffer *const buffer, const bool isDecayingDict)
+                : mHeaderPolicy(headerPolicy), mWritingHelper(writingHelper), mBuffer(buffer),
+                  mIsDecayingDict(isDecayingDict), mValueStack(), mChildrenValue(0),
+                  mValidUnigramCount(0) {}
+
+        ~TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted() {};
+
+        bool onAscend() {
+            if (mValueStack.empty()) {
+                return false;
+            }
+            mChildrenValue = mValueStack.back();
+            mValueStack.pop_back();
+            return true;
+        }
+
+        bool onDescend(const int ptNodeArrayPos) {
+            mValueStack.push_back(0);
+            mChildrenValue = 0;
+            return true;
+        }
+
+        bool onReadingPtNodeArrayTail() { return true; }
+
+        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints);
+
+        int getValidUnigramCount() const {
+            return mValidUnigramCount;
+        }
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(
+                TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted);
+
+        const DictionaryHeaderStructurePolicy *const mHeaderPolicy;
+        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
+        BufferWithExtendableBuffer *const mBuffer;
+        const bool mIsDecayingDict;
+        std::vector<int> mValueStack;
+        int mChildrenValue;
+        int mValidUnigramCount;
+    };
+
+    // Updates all bigram entries that are held by valid PtNodes. This removes useless bigram
+    // entries.
+    class TraversePolicyToUpdateBigramProbability
+            : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
+     public:
+        TraversePolicyToUpdateBigramProbability(
+                DynamicBigramListPolicy *const bigramPolicy)
+                : mBigramPolicy(bigramPolicy), mValidBigramEntryCount(0) {}
+
+        bool onAscend() { return true; }
+
+        bool onDescend(const int ptNodeArrayPos) { return true; }
+
+        bool onReadingPtNodeArrayTail() { return true; }
+
+        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints);
+
+        int getValidBigramEntryCount() const {
+            return mValidBigramEntryCount;
+        }
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(TraversePolicyToUpdateBigramProbability);
+
+        DynamicBigramListPolicy *const mBigramPolicy;
+        int mValidBigramEntryCount;
+    };
+
+    class TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+            : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
+     public:
+        TraversePolicyToPlaceAndWriteValidPtNodesToBuffer(
+                DynamicPatriciaTrieWritingHelper *const writingHelper,
+                BufferWithExtendableBuffer *const bufferToWrite,
+                DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
+                        dictPositionRelocationMap)
+                : mWritingHelper(writingHelper), mBufferToWrite(bufferToWrite),
+                  mDictPositionRelocationMap(dictPositionRelocationMap), mValidPtNodeCount(0),
+                  mPtNodeArraySizeFieldPos(NOT_A_DICT_POS) {};
+
+        bool onAscend() { return true; }
+
+        bool onDescend(const int ptNodeArrayPos);
+
+        bool onReadingPtNodeArrayTail();
+
+        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints);
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(TraversePolicyToPlaceAndWriteValidPtNodesToBuffer);
+
+        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
+        BufferWithExtendableBuffer *const mBufferToWrite;
+        DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
+                mDictPositionRelocationMap;
+        int mValidPtNodeCount;
+        int mPtNodeArraySizeFieldPos;
+    };
+
+    class TraversePolicyToUpdateAllPositionFields
+            : public DynamicPatriciaTrieReadingHelper::TraversingEventListener {
+     public:
+        TraversePolicyToUpdateAllPositionFields(
+                DynamicPatriciaTrieWritingHelper *const writingHelper,
+                DynamicBigramListPolicy *const bigramPolicy,
+                BufferWithExtendableBuffer *const bufferToWrite,
+                const DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
+                        dictPositionRelocationMap)
+                : mWritingHelper(writingHelper), mBigramPolicy(bigramPolicy),
+                  mBufferToWrite(bufferToWrite),
+                  mDictPositionRelocationMap(dictPositionRelocationMap), mUnigramCount(0),
+                  mBigramCount(0) {};
+
+        bool onAscend() { return true; }
+
+        bool onDescend(const int ptNodeArrayPos) { return true; }
+
+        bool onReadingPtNodeArrayTail() { return true; }
+
+        bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints);
+
+        int getUnigramCount() const {
+            return mUnigramCount;
+        }
+
+        int getBigramCount() const {
+            return mBigramCount;
+        }
+
+     private:
+        DISALLOW_IMPLICIT_CONSTRUCTORS(TraversePolicyToUpdateAllPositionFields);
+
+        DynamicPatriciaTrieWritingHelper *const mWritingHelper;
+        DynamicBigramListPolicy *const mBigramPolicy;
+        BufferWithExtendableBuffer *const mBufferToWrite;
+        const DynamicPatriciaTrieWritingHelper::DictPositionRelocationMap *const
+                mDictPositionRelocationMap;
+        int mUnigramCount;
+        int mBigramCount;
+    };
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieGcEventListeners);
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_GC_EVENT_LISTENERS_H */
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
new file mode 100644
index 0000000..2fa3111
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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_node_reader.h"
+
+#include "suggest/core/policy/dictionary_bigrams_structure_policy.h"
+#include "suggest/core/policy/dictionary_shortcuts_structure_policy.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+void DynamicPatriciaTrieNodeReader::fetchPtNodeInfoFromBufferAndProcessMovedPtNode(
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints) {
+    if (ptNodePos < 0 || ptNodePos >= mBuffer->getTailPosition()) {
+        // Reading invalid position because of bug or broken dictionary.
+        AKLOGE("Fetching PtNode info from invalid dictionary position: %d, dictionary size: %d",
+                ptNodePos, mBuffer->getTailPosition());
+        ASSERT(false);
+        invalidatePtNodeInfo();
+        return;
+    }
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(ptNodePos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    int pos = ptNodePos;
+    mHeadPos = ptNodePos;
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    mFlags = PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const int parentPosOffset =
+            DynamicPatriciaTrieReadingUtils::getParentPtNodePosOffsetAndAdvancePosition(dictBuf,
+                    &pos);
+    mParentPos = DynamicPatriciaTrieReadingUtils::getParentPtNodePos(parentPosOffset, mHeadPos);
+    if (outCodePoints != 0) {
+        mCodePointCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
+                dictBuf, mFlags, maxCodePointCount, outCodePoints, &pos);
+    } else {
+        mCodePointCount = PatriciaTrieReadingUtils::skipCharacters(
+                dictBuf, mFlags, MAX_WORD_LENGTH, &pos);
+    }
+    if (isTerminal()) {
+        mProbabilityFieldPos = pos;
+        if (usesAdditionalBuffer) {
+            mProbabilityFieldPos += mBuffer->getOriginalBufferSize();
+        }
+        mProbability = PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictBuf, &pos);
+    } else {
+        mProbabilityFieldPos = NOT_A_DICT_POS;
+        mProbability = NOT_A_PROBABILITY;
+    }
+    mChildrenPosFieldPos = pos;
+    if (usesAdditionalBuffer) {
+        mChildrenPosFieldPos += mBuffer->getOriginalBufferSize();
+    }
+    mChildrenPos = DynamicPatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+            dictBuf, &pos);
+    if (usesAdditionalBuffer && mChildrenPos != NOT_A_DICT_POS) {
+        mChildrenPos += mBuffer->getOriginalBufferSize();
+    }
+    if (mSiblingPos == NOT_A_DICT_POS) {
+        if (DynamicPatriciaTrieReadingUtils::isMoved(mFlags)) {
+            mBigramLinkedNodePos = mChildrenPos;
+        } else {
+            mBigramLinkedNodePos = NOT_A_DICT_POS;
+        }
+    }
+    if (usesAdditionalBuffer) {
+        pos += mBuffer->getOriginalBufferSize();
+    }
+    if (PatriciaTrieReadingUtils::hasShortcutTargets(mFlags)) {
+        mShortcutPos = pos;
+        mShortcutsPolicy->skipAllShortcuts(&pos);
+    } else {
+        mShortcutPos = NOT_A_DICT_POS;
+    }
+    if (PatriciaTrieReadingUtils::hasBigrams(mFlags)) {
+        mBigramPos = pos;
+        mBigramsPolicy->skipAllBigrams(&pos);
+    } else {
+        mBigramPos = NOT_A_DICT_POS;
+    }
+    // Update siblingPos if needed.
+    if (mSiblingPos == NOT_A_DICT_POS) {
+        // Sibling position is the tail position of current node.
+        mSiblingPos = pos;
+    }
+    // Read destination node if the read node is a moved node.
+    if (DynamicPatriciaTrieReadingUtils::isMoved(mFlags)) {
+        // The destination position is stored at the same place as the parent position.
+        fetchPtNodeInfoFromBufferAndProcessMovedPtNode(mParentPos, maxCodePointCount,
+                outCodePoints);
+    }
+}
+
+void DynamicPatriciaTrieNodeReader::invalidatePtNodeInfo() {
+    mHeadPos = NOT_A_DICT_POS;
+    mFlags = 0;
+    mParentPos = NOT_A_DICT_POS;
+    mCodePointCount = 0;
+    mProbabilityFieldPos = NOT_A_DICT_POS;
+    mProbability = NOT_A_PROBABILITY;
+    mChildrenPosFieldPos = NOT_A_DICT_POS;
+    mChildrenPos = NOT_A_DICT_POS;
+    mBigramLinkedNodePos = NOT_A_DICT_POS;
+    mShortcutPos = NOT_A_DICT_POS;
+    mBigramPos = NOT_A_DICT_POS;
+    mSiblingPos = NOT_A_DICT_POS;
+}
+
+}
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
new file mode 100644
index 0000000..3b36d42
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h
@@ -0,0 +1,163 @@
+/*
+ * 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_NODE_READER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DictionaryBigramsStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This class is used for helping to read nodes of dynamic patricia trie. This class handles moved
+ * node and reads node attributes.
+ */
+class DynamicPatriciaTrieNodeReader {
+ public:
+    DynamicPatriciaTrieNodeReader(const BufferWithExtendableBuffer *const buffer,
+            const DictionaryBigramsStructurePolicy *const bigramsPolicy,
+            const DictionaryShortcutsStructurePolicy *const shortcutsPolicy)
+            : mBuffer(buffer), mBigramsPolicy(bigramsPolicy),
+              mShortcutsPolicy(shortcutsPolicy), mHeadPos(NOT_A_DICT_POS), mFlags(0),
+              mParentPos(NOT_A_DICT_POS), mCodePointCount(0), mProbabilityFieldPos(NOT_A_DICT_POS),
+              mProbability(NOT_A_PROBABILITY), mChildrenPosFieldPos(NOT_A_DICT_POS),
+              mChildrenPos(NOT_A_DICT_POS), mBigramLinkedNodePos(NOT_A_DICT_POS),
+              mShortcutPos(NOT_A_DICT_POS),  mBigramPos(NOT_A_DICT_POS),
+              mSiblingPos(NOT_A_DICT_POS) {}
+
+    ~DynamicPatriciaTrieNodeReader() {}
+
+    // Reads PtNode information from dictionary buffer and updates members with the information.
+    AK_FORCE_INLINE void fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) {
+        fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(ptNodePos ,
+                0 /* maxCodePointCount */, 0 /* outCodePoints */);
+    }
+
+    AK_FORCE_INLINE void fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(
+            const int ptNodePos, const int maxCodePointCount, int *const outCodePoints) {
+        mSiblingPos = NOT_A_DICT_POS;
+        mBigramLinkedNodePos = NOT_A_DICT_POS;
+        fetchPtNodeInfoFromBufferAndProcessMovedPtNode(ptNodePos, maxCodePointCount, outCodePoints);
+    }
+
+    // HeadPos is different from NodePos when the current PtNode is a moved PtNode.
+    AK_FORCE_INLINE int getHeadPos() const {
+        return mHeadPos;
+    }
+
+    // Flags
+    AK_FORCE_INLINE bool isDeleted() const {
+        return DynamicPatriciaTrieReadingUtils::isDeleted(mFlags);
+    }
+
+    AK_FORCE_INLINE bool hasChildren() const {
+        return mChildrenPos != NOT_A_DICT_POS;
+    }
+
+    AK_FORCE_INLINE bool isTerminal() const {
+        return PatriciaTrieReadingUtils::isTerminal(mFlags);
+    }
+
+    AK_FORCE_INLINE bool isBlacklisted() const {
+        return PatriciaTrieReadingUtils::isBlacklisted(mFlags);
+    }
+
+    AK_FORCE_INLINE bool isNotAWord() const {
+        return PatriciaTrieReadingUtils::isNotAWord(mFlags);
+    }
+
+    // Parent node position
+    AK_FORCE_INLINE int getParentPos() const {
+        return mParentPos;
+    }
+
+    // Number of code points
+    AK_FORCE_INLINE uint8_t getCodePointCount() const {
+        return mCodePointCount;
+    }
+
+    // Probability
+    AK_FORCE_INLINE int getProbabilityFieldPos() const {
+        return mProbabilityFieldPos;
+    }
+
+    AK_FORCE_INLINE int getProbability() const {
+        return mProbability;
+    }
+
+    // Children PtNode array position
+    AK_FORCE_INLINE int getChildrenPosFieldPos() const {
+        return mChildrenPosFieldPos;
+    }
+
+    AK_FORCE_INLINE int getChildrenPos() const {
+        return mChildrenPos;
+    }
+
+    // Bigram linked node position.
+    AK_FORCE_INLINE int getBigramLinkedNodePos() const {
+        return mBigramLinkedNodePos;
+    }
+
+    // Shortcutlist position
+    AK_FORCE_INLINE int getShortcutPos() const {
+        return mShortcutPos;
+    }
+
+    // Bigrams position
+    AK_FORCE_INLINE int getBigramsPos() const {
+        return mBigramPos;
+    }
+
+    // Sibling node position
+    AK_FORCE_INLINE int getSiblingNodePos() const {
+        return mSiblingPos;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieNodeReader);
+
+    const BufferWithExtendableBuffer *const mBuffer;
+    const DictionaryBigramsStructurePolicy *const mBigramsPolicy;
+    const DictionaryShortcutsStructurePolicy *const mShortcutsPolicy;
+    int mHeadPos;
+    DynamicPatriciaTrieReadingUtils::NodeFlags mFlags;
+    int mParentPos;
+    uint8_t mCodePointCount;
+    int mProbabilityFieldPos;
+    int mProbability;
+    int mChildrenPosFieldPos;
+    int mChildrenPos;
+    int mBigramLinkedNodePos;
+    int mShortcutPos;
+    int mBigramPos;
+    int mSiblingPos;
+
+    void fetchPtNodeInfoFromBufferAndProcessMovedPtNode(const int ptNodePos,
+            const int maxCodePointCount, int *const outCodePoints);
+
+    void invalidatePtNodeInfo();
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_NODE_READER_H */
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
new file mode 100644
index 0000000..a8ea69f
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
@@ -0,0 +1,373 @@
+/*
+ * 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_policy.h"
+
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
+#include "suggest/policyimpl/dictionary/utils/probability_utils.h"
+
+namespace latinime {
+
+// Note that these are corresponding definitions in Java side in BinaryDictionaryTests and
+// BinaryDictionaryDecayingTests.
+const char *const DynamicPatriciaTriePolicy::UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
+const char *const DynamicPatriciaTriePolicy::SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY =
+        "SET_NEEDS_TO_DECAY_FOR_TESTING";
+const int DynamicPatriciaTriePolicy::MAX_DICT_EXTENDED_REGION_SIZE = 1024 * 1024;
+const int DynamicPatriciaTriePolicy::MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS =
+        DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE - 1024;
+
+void DynamicPatriciaTriePolicy::createAndGetAllChildNodes(const DicNode *const dicNode,
+        DicNodeVector *const childDicNodes) const {
+    if (!dicNode->hasChildren()) {
+        return;
+    }
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    readingHelper.initWithPtNodeArrayPos(dicNode->getChildrenPos());
+    const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
+    while (!readingHelper.isEnd()) {
+        childDicNodes->pushLeavingChild(dicNode, nodeReader->getHeadPos(),
+                nodeReader->getChildrenPos(), nodeReader->getProbability(),
+                nodeReader->isTerminal() && !nodeReader->isDeleted(),
+                nodeReader->hasChildren(), nodeReader->isBlacklisted() || nodeReader->isNotAWord(),
+                nodeReader->getCodePointCount(), readingHelper.getMergedNodeCodePoints());
+        readingHelper.readNextSiblingNode();
+    }
+}
+
+int DynamicPatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
+        const int ptNodePos, const int maxCodePointCount, int *const outCodePoints,
+        int *const outUnigramProbability) const {
+    // This method traverses parent nodes from the terminal by following parent pointers; thus,
+    // node code points are stored in the buffer in the reverse order.
+    int reverseCodePoints[maxCodePointCount];
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    // First, read the terminal node and get its probability.
+    readingHelper.initWithPtNodePos(ptNodePos);
+    if (!readingHelper.isValidTerminalNode()) {
+        // Node at the ptNodePos is not a valid terminal node.
+        *outUnigramProbability = NOT_A_PROBABILITY;
+        return 0;
+    }
+    // Store terminal node probability.
+    *outUnigramProbability = readingHelper.getNodeReader()->getProbability();
+    // Then, following parent node link to the dictionary root and fetch node code points.
+    while (!readingHelper.isEnd()) {
+        if (readingHelper.getTotalCodePointCount() > maxCodePointCount) {
+            // The ptNodePos is not a valid terminal node position in the dictionary.
+            *outUnigramProbability = NOT_A_PROBABILITY;
+            return 0;
+        }
+        // Store node code points to buffer in the reverse order.
+        readingHelper.fetchMergedNodeCodePointsInReverseOrder(
+                readingHelper.getPrevTotalCodePointCount(), reverseCodePoints);
+        // Follow parent node toward the root node.
+        readingHelper.readParentNode();
+    }
+    if (readingHelper.isError()) {
+        // The node position or the dictionary is invalid.
+        *outUnigramProbability = NOT_A_PROBABILITY;
+        return 0;
+    }
+    // Reverse the stored code points to output them.
+    const int codePointCount = readingHelper.getTotalCodePointCount();
+    for (int i = 0; i < codePointCount; ++i) {
+        outCodePoints[i] = reverseCodePoints[codePointCount - i - 1];
+    }
+    return codePointCount;
+}
+
+int DynamicPatriciaTriePolicy::getTerminalNodePositionOfWord(const int *const inWord,
+        const int length, const bool forceLowerCaseSearch) const {
+    int searchCodePoints[length];
+    for (int i = 0; i < length; ++i) {
+        searchCodePoints[i] = forceLowerCaseSearch ? CharUtils::toLowerCase(inWord[i]) : inWord[i];
+    }
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
+    const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
+    while (!readingHelper.isEnd()) {
+        const int matchedCodePointCount = readingHelper.getPrevTotalCodePointCount();
+        if (readingHelper.getTotalCodePointCount() > length
+                || !readingHelper.isMatchedCodePoint(0 /* index */,
+                        searchCodePoints[matchedCodePointCount])) {
+            // Current node has too many code points or its first code point is different from
+            // target code point. Skip this node and read the next sibling node.
+            readingHelper.readNextSiblingNode();
+            continue;
+        }
+        // Check following merged node code points.
+        const int nodeCodePointCount = nodeReader->getCodePointCount();
+        for (int j = 1; j < nodeCodePointCount; ++j) {
+            if (!readingHelper.isMatchedCodePoint(
+                    j, searchCodePoints[matchedCodePointCount + j])) {
+                // Different code point is found. The given word is not included in the dictionary.
+                return NOT_A_DICT_POS;
+            }
+        }
+        // All characters are matched.
+        if (length == readingHelper.getTotalCodePointCount()) {
+            // Terminal position is found.
+            return nodeReader->getHeadPos();
+        }
+        if (!nodeReader->hasChildren()) {
+            return NOT_A_DICT_POS;
+        }
+        // Advance to the children nodes.
+        readingHelper.readChildNode();
+    }
+    // If we already traversed the tree further than the word is long, there means
+    // there was no match (or we would have found it).
+    return NOT_A_DICT_POS;
+}
+
+int DynamicPatriciaTriePolicy::getProbability(const int unigramProbability,
+        const int bigramProbability) const {
+    if (mHeaderPolicy.isDecayingDict()) {
+        return ForgettingCurveUtils::getProbability(unigramProbability, bigramProbability);
+    } else {
+        if (unigramProbability == NOT_A_PROBABILITY) {
+            return NOT_A_PROBABILITY;
+        } else if (bigramProbability == NOT_A_PROBABILITY) {
+            return ProbabilityUtils::backoff(unigramProbability);
+        } else {
+            return ProbabilityUtils::computeProbabilityForBigram(unigramProbability,
+                    bigramProbability);
+        }
+    }
+}
+
+int DynamicPatriciaTriePolicy::getUnigramProbabilityOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_PROBABILITY;
+    }
+    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+    if (nodeReader.isDeleted() || nodeReader.isBlacklisted() || nodeReader.isNotAWord()) {
+        return NOT_A_PROBABILITY;
+    }
+    return getProbability(nodeReader.getProbability(), NOT_A_PROBABILITY);
+}
+
+int DynamicPatriciaTriePolicy::getShortcutPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
+    }
+    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+    if (nodeReader.isDeleted()) {
+        return NOT_A_DICT_POS;
+    }
+    return nodeReader.getShortcutPos();
+}
+
+int DynamicPatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return NOT_A_DICT_POS;
+    }
+    DynamicPatriciaTrieNodeReader nodeReader(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+    if (nodeReader.isDeleted()) {
+        return NOT_A_DICT_POS;
+    }
+    return nodeReader.getBigramsPos();
+}
+
+bool DynamicPatriciaTriePolicy::addUnigramWord(const int *const word, const int length,
+        const int probability) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: addUnigramWord() is called for non-updatable dictionary.");
+        return false;
+    }
+    if (mBufferWithExtendableBuffer.getTailPosition()
+            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
+        AKLOGE("The dictionary is too large to dynamically update.");
+        return false;
+    }
+    DynamicPatriciaTrieReadingHelper readingHelper(&mBufferWithExtendableBuffer,
+            getBigramsStructurePolicy(), getShortcutsStructurePolicy());
+    readingHelper.initWithPtNodeArrayPos(getRootPosition());
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy, mHeaderPolicy.isDecayingDict());
+    bool addedNewUnigram = false;
+    if (writingHelper.addUnigramWord(&readingHelper, word, length, probability,
+            &addedNewUnigram)) {
+        if (addedNewUnigram) {
+            mUnigramCount++;
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool DynamicPatriciaTriePolicy::addBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1, const int probability) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: addBigramWords() is called for non-updatable dictionary.");
+        return false;
+    }
+    if (mBufferWithExtendableBuffer.getTailPosition()
+            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
+        AKLOGE("The dictionary is too large to dynamically update.");
+        return false;
+    }
+    const int word0Pos = getTerminalNodePositionOfWord(word0, length0,
+            false /* forceLowerCaseSearch */);
+    if (word0Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    const int word1Pos = getTerminalNodePositionOfWord(word1, length1,
+            false /* forceLowerCaseSearch */);
+    if (word1Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy, mHeaderPolicy.isDecayingDict());
+    bool addedNewBigram = false;
+    if (writingHelper.addBigramWords(word0Pos, word1Pos, probability, &addedNewBigram)) {
+        if (addedNewBigram) {
+            mBigramCount++;
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+bool DynamicPatriciaTriePolicy::removeBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: removeBigramWords() is called for non-updatable dictionary.");
+        return false;
+    }
+    if (mBufferWithExtendableBuffer.getTailPosition()
+            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS) {
+        AKLOGE("The dictionary is too large to dynamically update.");
+        return false;
+    }
+    const int word0Pos = getTerminalNodePositionOfWord(word0, length0,
+            false /* forceLowerCaseSearch */);
+    if (word0Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    const int word1Pos = getTerminalNodePositionOfWord(word1, length1,
+            false /* forceLowerCaseSearch */);
+    if (word1Pos == NOT_A_DICT_POS) {
+        return false;
+    }
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy, mHeaderPolicy.isDecayingDict());
+    if (writingHelper.removeBigramWords(word0Pos, word1Pos)) {
+        mBigramCount--;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void DynamicPatriciaTriePolicy::flush(const char *const filePath) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: flush() is called for non-updatable dictionary.");
+        return;
+    }
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &mBigramListPolicy, &mShortcutListPolicy, false /* needsToDecay */);
+    writingHelper.writeToDictFile(filePath, &mHeaderPolicy, mUnigramCount, mBigramCount);
+}
+
+void DynamicPatriciaTriePolicy::flushWithGC(const char *const filePath) {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: flushWithGC() is called for non-updatable dictionary.");
+        return;
+    }
+    const bool needsToDecay = mHeaderPolicy.isDecayingDict()
+            && (mNeedsToDecayForTesting || ForgettingCurveUtils::needsToDecay(
+                    false /* mindsBlockByDecay */, mUnigramCount, mBigramCount, &mHeaderPolicy));
+    DynamicBigramListPolicy bigramListPolicyForGC(&mHeaderPolicy, &mBufferWithExtendableBuffer,
+            &mShortcutListPolicy, needsToDecay);
+    DynamicPatriciaTrieWritingHelper writingHelper(&mBufferWithExtendableBuffer,
+            &bigramListPolicyForGC, &mShortcutListPolicy, needsToDecay);
+    writingHelper.writeToDictFileWithGC(getRootPosition(), filePath, &mHeaderPolicy);
+    mNeedsToDecayForTesting = false;
+}
+
+bool DynamicPatriciaTriePolicy::needsToRunGC(const bool mindsBlockByGC) const {
+    if (!mBuffer->isUpdatable()) {
+        AKLOGI("Warning: needsToRunGC() is called for non-updatable dictionary.");
+        return false;
+    }
+    if (mBufferWithExtendableBuffer.isNearSizeLimit()) {
+        // Additional buffer size is near the limit.
+        return true;
+    } else if (mHeaderPolicy.getExtendedRegionSize()
+            + mBufferWithExtendableBuffer.getUsedAdditionalBufferSize()
+                    > MAX_DICT_EXTENDED_REGION_SIZE) {
+        // Total extended region size exceeds the limit.
+        return true;
+    } else if (mBufferWithExtendableBuffer.getTailPosition()
+            >= MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS
+                    && mBufferWithExtendableBuffer.getUsedAdditionalBufferSize() > 0) {
+        // Needs to reduce dictionary size.
+        return true;
+    } else if (mHeaderPolicy.isDecayingDict()) {
+        return mNeedsToDecayForTesting || ForgettingCurveUtils::needsToDecay(
+                mindsBlockByGC, mUnigramCount, mBigramCount, &mHeaderPolicy);
+    }
+    return false;
+}
+
+void DynamicPatriciaTriePolicy::getProperty(const char *const query, char *const outResult,
+        const int maxResultLength) {
+    if (strncmp(query, UNIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d", mUnigramCount);
+    } else if (strncmp(query, BIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d", mBigramCount);
+    } else if (strncmp(query, MAX_UNIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d",
+                mHeaderPolicy.isDecayingDict() ? ForgettingCurveUtils::MAX_UNIGRAM_COUNT :
+                        static_cast<int>(DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE));
+    } else if (strncmp(query, MAX_BIGRAM_COUNT_QUERY, maxResultLength) == 0) {
+        snprintf(outResult, maxResultLength, "%d",
+                mHeaderPolicy.isDecayingDict() ? ForgettingCurveUtils::MAX_BIGRAM_COUNT :
+                        static_cast<int>(DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE));
+    } else if (strncmp(query, SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY, maxResultLength) == 0) {
+        mNeedsToDecayForTesting = true;
+    }
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
new file mode 100644
index 0000000..be97ee1
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
@@ -0,0 +1,121 @@
+/*
+ * 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_POLICY_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_POLICY_H
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_structure_with_buffer_policy.h"
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+#include "suggest/policyimpl/dictionary/utils/mmapped_buffer.h"
+
+namespace latinime {
+
+class DicNode;
+class DicNodeVector;
+
+class DynamicPatriciaTriePolicy : public DictionaryStructureWithBufferPolicy {
+ public:
+    DynamicPatriciaTriePolicy(const MmappedBuffer *const buffer)
+            : mBuffer(buffer), mHeaderPolicy(mBuffer->getBuffer(), buffer->getBufferSize()),
+              mBufferWithExtendableBuffer(mBuffer->getBuffer() + mHeaderPolicy.getSize(),
+                      mBuffer->getBufferSize() - mHeaderPolicy.getSize()),
+              mShortcutListPolicy(&mBufferWithExtendableBuffer),
+              mBigramListPolicy(&mHeaderPolicy, &mBufferWithExtendableBuffer, &mShortcutListPolicy,
+                      mHeaderPolicy.isDecayingDict()),
+              mUnigramCount(mHeaderPolicy.getUnigramCount()),
+              mBigramCount(mHeaderPolicy.getBigramCount()), mNeedsToDecayForTesting(false) {}
+
+    ~DynamicPatriciaTriePolicy() {
+        delete mBuffer;
+    }
+
+    AK_FORCE_INLINE int getRootPosition() const {
+        return 0;
+    }
+
+    void createAndGetAllChildNodes(const DicNode *const dicNode,
+            DicNodeVector *const childDicNodes) const;
+
+    int getCodePointsAndProbabilityAndReturnCodePointCount(
+            const int terminalPtNodePos, const int maxCodePointCount, int *const outCodePoints,
+            int *const outUnigramProbability) const;
+
+    int getTerminalNodePositionOfWord(const int *const inWord,
+            const int length, const bool forceLowerCaseSearch) const;
+
+    int getProbability(const int unigramProbability, const int bigramProbability) const;
+
+    int getUnigramProbabilityOfPtNode(const int ptNodePos) const;
+
+    int getShortcutPositionOfPtNode(const int ptNodePos) const;
+
+    int getBigramsPositionOfPtNode(const int ptNodePos) const;
+
+    const DictionaryHeaderStructurePolicy *getHeaderStructurePolicy() const {
+        return &mHeaderPolicy;
+    }
+
+    const DictionaryBigramsStructurePolicy *getBigramsStructurePolicy() const {
+        return &mBigramListPolicy;
+    }
+
+    const DictionaryShortcutsStructurePolicy *getShortcutsStructurePolicy() const {
+        return &mShortcutListPolicy;
+    }
+
+    bool addUnigramWord(const int *const word, const int length, const int probability);
+
+    bool addBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1, const int probability);
+
+    bool removeBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1);
+
+    void flush(const char *const filePath);
+
+    void flushWithGC(const char *const filePath);
+
+    bool needsToRunGC(const bool mindsBlockByGC) const;
+
+    void getProperty(const char *const query, char *const outResult,
+            const int maxResultLength);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTriePolicy);
+
+    static const char *const UNIGRAM_COUNT_QUERY;
+    static const char *const BIGRAM_COUNT_QUERY;
+    static const char *const MAX_UNIGRAM_COUNT_QUERY;
+    static const char *const MAX_BIGRAM_COUNT_QUERY;
+    static const char *const SET_NEEDS_TO_DECAY_FOR_TESTING_QUERY;
+    static const int MAX_DICT_EXTENDED_REGION_SIZE;
+    static const int MIN_DICT_SIZE_TO_REFUSE_DYNAMIC_OPERATIONS;
+
+    const MmappedBuffer *const mBuffer;
+    const HeaderPolicy mHeaderPolicy;
+    BufferWithExtendableBuffer mBufferWithExtendableBuffer;
+    DynamicShortcutListPolicy mShortcutListPolicy;
+    DynamicBigramListPolicy mBigramListPolicy;
+    int mUnigramCount;
+    int mBigramCount;
+    int mNeedsToDecayForTesting;
+};
+} // namespace latinime
+#endif // LATINIME_DYNAMIC_PATRICIA_TRIE_POLICY_H
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
new file mode 100644
index 0000000..f108c21
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.cpp
@@ -0,0 +1,239 @@
+/*
+ * 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_helper.h"
+
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+// To avoid infinite loop caused by invalid or malicious forward links.
+const int DynamicPatriciaTrieReadingHelper::MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
+const int DynamicPatriciaTrieReadingHelper::MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP = 100000;
+const size_t DynamicPatriciaTrieReadingHelper::MAX_READING_STATE_STACK_SIZE = MAX_WORD_LENGTH;
+
+// Visits all PtNodes in post-order depth first manner.
+// For example, visits c -> b -> y -> x -> a for the following dictionary:
+// a _ b _ c
+//   \ x _ y
+bool DynamicPatriciaTrieReadingHelper::traverseAllPtNodesInPostorderDepthFirstManner(
+        TraversingEventListener *const listener) {
+    bool alreadyVisitedChildren = false;
+    // Descend from the root to the root PtNode array.
+    if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
+        return false;
+    }
+    while (!isEnd()) {
+        if (!alreadyVisitedChildren) {
+            if (mNodeReader.hasChildren()) {
+                // Move to the first child.
+                if (!listener->onDescend(mNodeReader.getChildrenPos())) {
+                    return false;
+                }
+                pushReadingStateToStack();
+                readChildNode();
+            } else {
+                alreadyVisitedChildren = true;
+            }
+        } else {
+            if (!listener->onVisitingPtNode(&mNodeReader, mMergedNodeCodePoints)) {
+                return false;
+            }
+            readNextSiblingNode();
+            if (isEnd()) {
+                // All PtNodes in current linked PtNode arrays have been visited.
+                // Return to the parent.
+                if (!listener->onReadingPtNodeArrayTail()) {
+                    return false;
+                }
+                if (mReadingStateStack.size() <= 0) {
+                    break;
+                }
+                if (!listener->onAscend()) {
+                    return false;
+                }
+                popReadingStateFromStack();
+                alreadyVisitedChildren = true;
+            } else {
+                // Process sibling PtNode.
+                alreadyVisitedChildren = false;
+            }
+        }
+    }
+    // Ascend from the root PtNode array to the root.
+    if (!listener->onAscend()) {
+        return false;
+    }
+    return !isError();
+}
+
+// Visits all PtNodes in PtNode array level pre-order depth first manner, which is the same order
+// that PtNodes are written in the dictionary buffer.
+// For example, visits a -> b -> x -> c -> y for the following dictionary:
+// a _ b _ c
+//   \ x _ y
+bool DynamicPatriciaTrieReadingHelper::traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+        TraversingEventListener *const listener) {
+    bool alreadyVisitedAllPtNodesInArray = false;
+    bool alreadyVisitedChildren = false;
+    // Descend from the root to the root PtNode array.
+    if (!listener->onDescend(getPosOfLastPtNodeArrayHead())) {
+        return false;
+    }
+    if (isEnd()) {
+        // Empty dictionary. Needs to notify the listener of the tail of empty PtNode array.
+        if (!listener->onReadingPtNodeArrayTail()) {
+            return false;
+        }
+    }
+    pushReadingStateToStack();
+    while (!isEnd()) {
+        if (alreadyVisitedAllPtNodesInArray) {
+            if (alreadyVisitedChildren) {
+                // Move to next sibling PtNode's children.
+                readNextSiblingNode();
+                if (isEnd()) {
+                    // Return to the parent PTNode.
+                    if (!listener->onAscend()) {
+                        return false;
+                    }
+                    if (mReadingStateStack.size() <= 0) {
+                        break;
+                    }
+                    popReadingStateFromStack();
+                    alreadyVisitedChildren = true;
+                    alreadyVisitedAllPtNodesInArray = true;
+                } else {
+                    alreadyVisitedChildren = false;
+                }
+            } else {
+                if (mNodeReader.hasChildren()) {
+                    // Move to the first child.
+                    if (!listener->onDescend(mNodeReader.getChildrenPos())) {
+                        return false;
+                    }
+                    pushReadingStateToStack();
+                    readChildNode();
+                    // Push state to return the head of PtNode array.
+                    pushReadingStateToStack();
+                    alreadyVisitedAllPtNodesInArray = false;
+                    alreadyVisitedChildren = false;
+                } else {
+                    alreadyVisitedChildren = true;
+                }
+            }
+        } else {
+            if (!listener->onVisitingPtNode(&mNodeReader, mMergedNodeCodePoints)) {
+                return false;
+            }
+            readNextSiblingNode();
+            if (isEnd()) {
+                if (!listener->onReadingPtNodeArrayTail()) {
+                    return false;
+                }
+                // Return to the head of current PtNode array.
+                popReadingStateFromStack();
+                alreadyVisitedAllPtNodesInArray = true;
+            }
+        }
+    }
+    popReadingStateFromStack();
+    // Ascend from the root PtNode array to the root.
+    if (!listener->onAscend()) {
+        return false;
+    }
+    return !isError();
+}
+
+// Read node array size and process empty node arrays. Nodes and arrays are counted up in this
+// method to avoid an infinite loop.
+void DynamicPatriciaTrieReadingHelper::nextPtNodeArray() {
+    if (mReadingState.mPos < 0 || mReadingState.mPos >= mBuffer->getTailPosition()) {
+        // Reading invalid position because of a bug or a broken dictionary.
+        AKLOGE("Reading PtNode array info from invalid dictionary position: %d, dict size: %d",
+                mReadingState.mPos, mBuffer->getTailPosition());
+        ASSERT(false);
+        mIsError = true;
+        mReadingState.mPos = NOT_A_DICT_POS;
+        return;
+    }
+    mReadingState.mPosOfLastPtNodeArrayHead = mReadingState.mPos;
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
+    }
+    mReadingState.mNodeCount = PatriciaTrieReadingUtils::getPtNodeArraySizeAndAdvancePosition(
+            dictBuf, &mReadingState.mPos);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos += mBuffer->getOriginalBufferSize();
+    }
+    // Count up nodes and node arrays to avoid infinite loop.
+    mReadingState.mTotalNodeCount += mReadingState.mNodeCount;
+    mReadingState.mNodeArrayCount++;
+    if (mReadingState.mNodeCount < 0
+            || mReadingState.mTotalNodeCount > MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP
+            || mReadingState.mNodeArrayCount > MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP) {
+        // Invalid dictionary.
+        AKLOGI("Invalid dictionary. nodeCount: %d, totalNodeCount: %d, MAX_CHILD_COUNT: %d"
+                "nodeArrayCount: %d, MAX_NODE_ARRAY_COUNT: %d",
+                mReadingState.mNodeCount, mReadingState.mTotalNodeCount,
+                MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP, mReadingState.mNodeArrayCount,
+                MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP);
+        ASSERT(false);
+        mIsError = true;
+        mReadingState.mPos = NOT_A_DICT_POS;
+        return;
+    }
+    if (mReadingState.mNodeCount == 0) {
+        // Empty node array. Try following forward link.
+        followForwardLink();
+    }
+}
+
+// Follow the forward link and read the next node array if exists.
+void DynamicPatriciaTrieReadingHelper::followForwardLink() {
+    if (mReadingState.mPos < 0 || mReadingState.mPos >= mBuffer->getTailPosition()) {
+        // Reading invalid position because of bug or broken dictionary.
+        AKLOGE("Reading forward link from invalid dictionary position: %d, dict size: %d",
+                mReadingState.mPos, mBuffer->getTailPosition());
+        ASSERT(false);
+        mIsError = true;
+        mReadingState.mPos = NOT_A_DICT_POS;
+        return;
+    }
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(mReadingState.mPos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos -= mBuffer->getOriginalBufferSize();
+    }
+    const int forwardLinkPosition =
+            DynamicPatriciaTrieReadingUtils::getForwardLinkPosition(dictBuf, mReadingState.mPos);
+    if (usesAdditionalBuffer) {
+        mReadingState.mPos += mBuffer->getOriginalBufferSize();
+    }
+    mReadingState.mPosOfLastForwardLinkField = mReadingState.mPos;
+    if (DynamicPatriciaTrieReadingUtils::isValidForwardLinkPosition(forwardLinkPosition)) {
+        // Follow the forward link.
+        mReadingState.mPos += forwardLinkPosition;
+        nextPtNodeArray();
+    } else {
+        // All node arrays have been read.
+        mReadingState.mPos = NOT_A_DICT_POS;
+    }
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
new file mode 100644
index 0000000..a71c069
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h
@@ -0,0 +1,289 @@
+/*
+ * 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_HELPER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H
+
+#include <cstddef>
+#include <vector>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DictionaryBigramsStructurePolicy;
+class DictionaryShortcutsStructurePolicy;
+
+/*
+ * This class is used for traversing dynamic patricia trie. This class supports iterating nodes and
+ * dealing with additional buffer. This class counts nodes and node arrays to avoid infinite loop.
+ */
+class DynamicPatriciaTrieReadingHelper {
+ public:
+    class TraversingEventListener {
+     public:
+        virtual ~TraversingEventListener() {};
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onAscend() = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onDescend(const int ptNodeArrayPos) = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onReadingPtNodeArrayTail() = 0;
+
+        // Returns whether the event handling was succeeded or not.
+        virtual bool onVisitingPtNode(const DynamicPatriciaTrieNodeReader *const node,
+                const int *const nodeCodePoints) = 0;
+
+     protected:
+        TraversingEventListener() {};
+
+     private:
+        DISALLOW_COPY_AND_ASSIGN(TraversingEventListener);
+    };
+
+    DynamicPatriciaTrieReadingHelper(const BufferWithExtendableBuffer *const buffer,
+            const DictionaryBigramsStructurePolicy *const bigramsPolicy,
+            const DictionaryShortcutsStructurePolicy *const shortcutsPolicy)
+            : mIsError(false), mReadingState(), mBuffer(buffer),
+              mNodeReader(mBuffer, bigramsPolicy, shortcutsPolicy), mReadingStateStack() {}
+
+    ~DynamicPatriciaTrieReadingHelper() {}
+
+    AK_FORCE_INLINE bool isError() const {
+        return mIsError;
+    }
+
+    AK_FORCE_INLINE bool isEnd() const {
+        return mReadingState.mPos == NOT_A_DICT_POS;
+    }
+
+    // Initialize reading state with the head position of a PtNode array.
+    AK_FORCE_INLINE void initWithPtNodeArrayPos(const int ptNodeArrayPos) {
+        if (ptNodeArrayPos == NOT_A_DICT_POS) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mIsError = false;
+            mReadingState.mPos = ptNodeArrayPos;
+            mReadingState.mPrevTotalCodePointCount = 0;
+            mReadingState.mTotalNodeCount = 0;
+            mReadingState.mNodeArrayCount = 0;
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingStateStack.clear();
+            nextPtNodeArray();
+            if (!isEnd()) {
+                fetchPtNodeInfo();
+            }
+        }
+    }
+
+    // Initialize reading state with the head position of a node.
+    AK_FORCE_INLINE void initWithPtNodePos(const int ptNodePos) {
+        if (ptNodePos == NOT_A_DICT_POS) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mIsError = false;
+            mReadingState.mPos = ptNodePos;
+            mReadingState.mNodeCount = 1;
+            mReadingState.mPrevTotalCodePointCount = 0;
+            mReadingState.mTotalNodeCount = 1;
+            mReadingState.mNodeArrayCount = 1;
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingState.mPosOfLastPtNodeArrayHead = NOT_A_DICT_POS;
+            mReadingStateStack.clear();
+            fetchPtNodeInfo();
+        }
+    }
+
+    AK_FORCE_INLINE const DynamicPatriciaTrieNodeReader* getNodeReader() const {
+        return &mNodeReader;
+    }
+
+    AK_FORCE_INLINE bool isValidTerminalNode() const {
+        return !isEnd() && !mNodeReader.isDeleted() && mNodeReader.isTerminal();
+    }
+
+    AK_FORCE_INLINE bool isMatchedCodePoint(const int index, const int codePoint) const {
+        return mMergedNodeCodePoints[index] == codePoint;
+    }
+
+    // Return code point count exclude the last read node's code points.
+    AK_FORCE_INLINE int getPrevTotalCodePointCount() const {
+        return mReadingState.mPrevTotalCodePointCount;
+    }
+
+    // Return code point count include the last read node's code points.
+    AK_FORCE_INLINE int getTotalCodePointCount() const {
+        return mReadingState.mPrevTotalCodePointCount + mNodeReader.getCodePointCount();
+    }
+
+    AK_FORCE_INLINE void fetchMergedNodeCodePointsInReverseOrder(
+            const int index, int *const outCodePoints) const {
+        const int nodeCodePointCount = mNodeReader.getCodePointCount();
+        for (int i =  0; i < nodeCodePointCount; ++i) {
+            outCodePoints[index + i] = mMergedNodeCodePoints[nodeCodePointCount - 1 - i];
+        }
+    }
+
+    AK_FORCE_INLINE const int *getMergedNodeCodePoints() const {
+        return mMergedNodeCodePoints;
+    }
+
+    AK_FORCE_INLINE void readNextSiblingNode() {
+        mReadingState.mNodeCount -= 1;
+        mReadingState.mPos = mNodeReader.getSiblingNodePos();
+        if (mReadingState.mNodeCount <= 0) {
+            // All nodes in the current node array have been read.
+            followForwardLink();
+            if (!isEnd()) {
+                fetchPtNodeInfo();
+            }
+        } else {
+            fetchPtNodeInfo();
+        }
+    }
+
+    // Read the first child node of the current node.
+    AK_FORCE_INLINE void readChildNode() {
+        if (mNodeReader.hasChildren()) {
+            mReadingState.mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
+            mReadingState.mTotalNodeCount = 0;
+            mReadingState.mNodeArrayCount = 0;
+            mReadingState.mPos = mNodeReader.getChildrenPos();
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            // Read children node array.
+            nextPtNodeArray();
+            if (!isEnd()) {
+                fetchPtNodeInfo();
+            }
+        } else {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        }
+    }
+
+    // Read the parent node of the current node.
+    AK_FORCE_INLINE void readParentNode() {
+        if (mNodeReader.getParentPos() != NOT_A_DICT_POS) {
+            mReadingState.mPrevTotalCodePointCount += mNodeReader.getCodePointCount();
+            mReadingState.mTotalNodeCount = 1;
+            mReadingState.mNodeArrayCount = 1;
+            mReadingState.mNodeCount = 1;
+            mReadingState.mPos = mNodeReader.getParentPos();
+            mReadingState.mPosOfLastForwardLinkField = NOT_A_DICT_POS;
+            mReadingState.mPosOfLastPtNodeArrayHead = NOT_A_DICT_POS;
+            fetchPtNodeInfo();
+        } else {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        }
+    }
+
+    AK_FORCE_INLINE int getPosOfLastForwardLinkField() const {
+        return mReadingState.mPosOfLastForwardLinkField;
+    }
+
+    AK_FORCE_INLINE int getPosOfLastPtNodeArrayHead() const {
+        return mReadingState.mPosOfLastPtNodeArrayHead;
+    }
+
+    AK_FORCE_INLINE void reloadCurrentPtNodeInfo() {
+        if (!isEnd()) {
+            fetchPtNodeInfo();
+        }
+    }
+
+    bool traverseAllPtNodesInPostorderDepthFirstManner(TraversingEventListener *const listener);
+
+    bool traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            TraversingEventListener *const listener);
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTrieReadingHelper);
+
+    class ReadingState {
+     public:
+        // Note that copy constructor and assignment operator are used for this class to use
+        // std::vector.
+        ReadingState() : mPos(NOT_A_DICT_POS), mNodeCount(0), mPrevTotalCodePointCount(0),
+                mTotalNodeCount(0), mNodeArrayCount(0), mPosOfLastForwardLinkField(NOT_A_DICT_POS),
+                mPosOfLastPtNodeArrayHead(NOT_A_DICT_POS) {}
+
+        int mPos;
+        // Node count of a node array.
+        int mNodeCount;
+        int mPrevTotalCodePointCount;
+        int mTotalNodeCount;
+        int mNodeArrayCount;
+        int mPosOfLastForwardLinkField;
+        int mPosOfLastPtNodeArrayHead;
+    };
+
+    static const int MAX_CHILD_COUNT_TO_AVOID_INFINITE_LOOP;
+    static const int MAX_NODE_ARRAY_COUNT_TO_AVOID_INFINITE_LOOP;
+    static const size_t MAX_READING_STATE_STACK_SIZE;
+
+    // TODO: Introduce error code to track what caused the error.
+    bool mIsError;
+    ReadingState mReadingState;
+    const BufferWithExtendableBuffer *const mBuffer;
+    DynamicPatriciaTrieNodeReader mNodeReader;
+    int mMergedNodeCodePoints[MAX_WORD_LENGTH];
+    std::vector<ReadingState> mReadingStateStack;
+
+    void nextPtNodeArray();
+
+    void followForwardLink();
+
+    AK_FORCE_INLINE void fetchPtNodeInfo() {
+        mNodeReader.fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(mReadingState.mPos,
+                MAX_WORD_LENGTH, mMergedNodeCodePoints);
+        if (mNodeReader.getCodePointCount() <= 0) {
+            // Empty node is not allowed.
+            mIsError = true;
+            mReadingState.mPos = NOT_A_DICT_POS;
+        }
+    }
+
+    AK_FORCE_INLINE void pushReadingStateToStack() {
+        if (mReadingStateStack.size() > MAX_READING_STATE_STACK_SIZE) {
+            AKLOGI("Reading state stack overflow. Max size: %zd", MAX_READING_STATE_STACK_SIZE);
+            ASSERT(false);
+            mIsError = true;
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mReadingStateStack.push_back(mReadingState);
+        }
+    }
+
+    AK_FORCE_INLINE void popReadingStateFromStack() {
+        if (mReadingStateStack.empty()) {
+            mReadingState.mPos = NOT_A_DICT_POS;
+        } else {
+            mReadingState = mReadingStateStack.back();
+            mReadingStateStack.pop_back();
+            if (!isEnd()) {
+                fetchPtNodeInfo();
+            }
+        }
+    }
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_READING_HELPER_H */
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
new file mode 100644
index 0000000..d68446d
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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/policyimpl/dictionary/utils/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;
+
+// TODO: Make DICT_OFFSET_ZERO_OFFSET = 0.
+// Currently, DICT_OFFSET_INVALID is 0 in Java side but offset can be 0 during GC. So, the maximum
+// value of offsets, which is 0x7FFFFF is used to represent 0 offset.
+const int DptReadingUtils::DICT_OFFSET_INVALID = 0;
+const int DptReadingUtils::DICT_OFFSET_ZERO_OFFSET = 0x7FFFFF;
+
+/* static */ int DptReadingUtils::getForwardLinkPosition(const uint8_t *const buffer,
+        const int pos) {
+    int linkAddressPos = pos;
+    return ByteArrayUtils::readSint24AndAdvancePosition(buffer, &linkAddressPos);
+}
+
+/* static */ int DptReadingUtils::getParentPtNodePosOffsetAndAdvancePosition(
+        const uint8_t *const buffer, int *const pos) {
+    return ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+}
+
+/* static */ int DptReadingUtils::getParentPtNodePos(const int parentOffset, const int ptNodePos) {
+    if (parentOffset == DICT_OFFSET_INVALID) {
+        return NOT_A_DICT_POS;
+    } else if (parentOffset == DICT_OFFSET_ZERO_OFFSET) {
+        return ptNodePos;
+    } else {
+        return parentOffset + ptNodePos;
+    }
+}
+
+/* static */ int DptReadingUtils::readChildrenPositionAndAdvancePosition(
+        const uint8_t *const buffer, int *const pos) {
+    const int base = *pos;
+    const int offset = ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+    if (offset == DICT_OFFSET_INVALID) {
+        // The PtNode does not have children.
+        return NOT_A_DICT_POS;
+    } else if (offset == DICT_OFFSET_ZERO_OFFSET) {
+        return base;
+    } else {
+        return base + offset;
+    }
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
new file mode 100644
index 0000000..67c3cc5
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
@@ -0,0 +1,75 @@
+/*
+ * 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"
+
+namespace latinime {
+
+class DynamicPatriciaTrieReadingUtils {
+ public:
+    typedef uint8_t NodeFlags;
+
+    static const int DICT_OFFSET_INVALID;
+    static const int DICT_OFFSET_ZERO_OFFSET;
+
+    static int getForwardLinkPosition(const uint8_t *const buffer, const int pos);
+
+    static AK_FORCE_INLINE bool isValidForwardLinkPosition(const int forwardLinkAddress) {
+        return forwardLinkAddress != 0;
+    }
+
+    static int getParentPtNodePosOffsetAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos);
+
+    static int getParentPtNodePos(const int parentOffset, const int ptNodePos);
+
+    static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer, 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);
+    }
+
+    static AK_FORCE_INLINE NodeFlags updateAndGetFlags(const NodeFlags originalFlags,
+            const bool isMoved, const bool isDeleted) {
+        NodeFlags flags = originalFlags;
+        flags = isMoved ? ((flags & (~MASK_MOVED)) | FLAG_IS_MOVED) : flags;
+        flags = isDeleted ? ((flags & (~MASK_MOVED)) | FLAG_IS_DELETED) : flags;
+        flags = (!isMoved && !isDeleted) ? ((flags & (~MASK_MOVED)) | FLAG_IS_NOT_MOVED) : flags;
+        return 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/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
new file mode 100644
index 0000000..052558b
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.cpp
@@ -0,0 +1,558 @@
+/*
+ * 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_writing_helper.h"
+
+#include "suggest/policyimpl/dictionary/bigram/dynamic_bigram_list_policy.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_gc_event_listeners.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_node_reader.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_helper.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h"
+#include "suggest/policyimpl/dictionary/header/header_policy.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+#include "suggest/policyimpl/dictionary/shortcut/dynamic_shortcut_list_policy.h"
+#include "suggest/policyimpl/dictionary/utils/dict_file_writing_utils.h"
+#include "suggest/policyimpl/dictionary/utils/forgetting_curve_utils.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+const int DynamicPatriciaTrieWritingHelper::CHILDREN_POSITION_FIELD_SIZE = 3;
+// TODO: Make MAX_DICTIONARY_SIZE 8MB.
+const size_t DynamicPatriciaTrieWritingHelper::MAX_DICTIONARY_SIZE = 2 * 1024 * 1024;
+
+bool DynamicPatriciaTrieWritingHelper::addUnigramWord(
+        DynamicPatriciaTrieReadingHelper *const readingHelper,
+        const int *const wordCodePoints, const int codePointCount, const int probability,
+        bool *const outAddedNewUnigram) {
+    int parentPos = NOT_A_DICT_POS;
+    while (!readingHelper->isEnd()) {
+        const int matchedCodePointCount = readingHelper->getPrevTotalCodePointCount();
+        if (!readingHelper->isMatchedCodePoint(0 /* index */,
+                wordCodePoints[matchedCodePointCount])) {
+            // The first code point is different from target code point. Skip this node and read
+            // the next sibling node.
+            readingHelper->readNextSiblingNode();
+            continue;
+        }
+        // Check following merged node code points.
+        const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper->getNodeReader();
+        const int nodeCodePointCount = nodeReader->getCodePointCount();
+        for (int j = 1; j < nodeCodePointCount; ++j) {
+            const int nextIndex = matchedCodePointCount + j;
+            if (nextIndex >= codePointCount || !readingHelper->isMatchedCodePoint(j,
+                    wordCodePoints[matchedCodePointCount + j])) {
+                *outAddedNewUnigram = true;
+                return reallocatePtNodeAndAddNewPtNodes(nodeReader,
+                        readingHelper->getMergedNodeCodePoints(), j,
+                        getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */,
+                                probability),
+                        wordCodePoints + matchedCodePointCount,
+                        codePointCount - matchedCodePointCount);
+            }
+        }
+        // All characters are matched.
+        if (codePointCount == readingHelper->getTotalCodePointCount()) {
+            return setPtNodeProbability(nodeReader, probability,
+                    readingHelper->getMergedNodeCodePoints(), outAddedNewUnigram);
+        }
+        if (!nodeReader->hasChildren()) {
+            *outAddedNewUnigram = true;
+            return createChildrenPtNodeArrayAndAChildPtNode(nodeReader,
+                    getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability),
+                    wordCodePoints + readingHelper->getTotalCodePointCount(),
+                    codePointCount - readingHelper->getTotalCodePointCount());
+        }
+        // Advance to the children nodes.
+        parentPos = nodeReader->getHeadPos();
+        readingHelper->readChildNode();
+    }
+    if (readingHelper->isError()) {
+        // The dictionary is invalid.
+        return false;
+    }
+    int pos = readingHelper->getPosOfLastForwardLinkField();
+    *outAddedNewUnigram = true;
+    return createAndInsertNodeIntoPtNodeArray(parentPos,
+            wordCodePoints + readingHelper->getPrevTotalCodePointCount(),
+            codePointCount - readingHelper->getPrevTotalCodePointCount(),
+            getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability), &pos);
+}
+
+bool DynamicPatriciaTrieWritingHelper::addBigramWords(const int word0Pos, const int word1Pos,
+        const int probability, bool *const outAddedNewBigram) {
+    int mMergedNodeCodePoints[MAX_WORD_LENGTH];
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePosAndGetNodeCodePoints(word0Pos, MAX_WORD_LENGTH,
+            mMergedNodeCodePoints);
+    // Move node to add bigram entry.
+    const int newNodePos = mBuffer->getTailPosition();
+    if (!markNodeAsMovedAndSetPosition(&nodeReader, newNodePos, newNodePos)) {
+        return false;
+    }
+    int writingPos = newNodePos;
+    // Write a new PtNode using original PtNode's info to the tail of the dictionary in mBuffer.
+    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, &nodeReader, nodeReader.getParentPos(),
+            mMergedNodeCodePoints, nodeReader.getCodePointCount(), nodeReader.getProbability(),
+            &writingPos)) {
+        return false;
+    }
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(newNodePos);
+    if (nodeReader.getBigramsPos() != NOT_A_DICT_POS) {
+        // Insert a new bigram entry into the existing bigram list.
+        int bigramListPos = nodeReader.getBigramsPos();
+        return mBigramPolicy->addNewBigramEntryToBigramList(word1Pos, probability, &bigramListPos,
+                outAddedNewBigram);
+    } else {
+        // The PtNode doesn't have a bigram list.
+        *outAddedNewBigram = true;
+        // First, Write a bigram entry at the tail position of the PtNode.
+        if (!mBigramPolicy->writeNewBigramEntry(word1Pos, probability, &writingPos)) {
+            return false;
+        }
+        // Then, Mark as the PtNode having bigram list in the flags.
+        const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+                PatriciaTrieReadingUtils::createAndGetFlags(nodeReader.isBlacklisted(),
+                        nodeReader.isNotAWord(), nodeReader.getProbability() != NOT_A_PROBABILITY,
+                        nodeReader.getShortcutPos() != NOT_A_DICT_POS, true /* hasBigrams */,
+                        nodeReader.getCodePointCount() > 1, CHILDREN_POSITION_FIELD_SIZE);
+        writingPos = newNodePos;
+        // Write updated flags into the moved PtNode's flags field.
+        return DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+                &writingPos);
+    }
+}
+
+// Remove a bigram relation from word0Pos to word1Pos.
+bool DynamicPatriciaTrieWritingHelper::removeBigramWords(const int word0Pos, const int word1Pos) {
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(word0Pos);
+    if (nodeReader.getBigramsPos() == NOT_A_DICT_POS) {
+        return false;
+    }
+    return mBigramPolicy->removeBigram(nodeReader.getBigramsPos(), word1Pos);
+}
+
+void DynamicPatriciaTrieWritingHelper::writeToDictFile(const char *const fileName,
+        const HeaderPolicy *const headerPolicy, const int unigramCount, const int bigramCount) {
+    BufferWithExtendableBuffer headerBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
+    const int extendedRegionSize = headerPolicy->getExtendedRegionSize() +
+            mBuffer->getUsedAdditionalBufferSize();
+    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, false /* updatesLastUpdatedTime */,
+            false /* updatesLastDecayedTime */, unigramCount, bigramCount, extendedRegionSize)) {
+        return;
+    }
+    DictFileWritingUtils::flushAllHeaderAndBodyToFile(fileName, &headerBuffer, mBuffer);
+}
+
+void DynamicPatriciaTrieWritingHelper::writeToDictFileWithGC(const int rootPtNodeArrayPos,
+        const char *const fileName, const HeaderPolicy *const headerPolicy) {
+    BufferWithExtendableBuffer newDictBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */,
+            MAX_DICTIONARY_SIZE);
+    int unigramCount = 0;
+    int bigramCount = 0;
+    if (mNeedsToDecay) {
+        ForgettingCurveUtils::sTimeKeeper.setCurrentTime();
+    }
+    if (!runGC(rootPtNodeArrayPos, headerPolicy, &newDictBuffer, &unigramCount, &bigramCount)) {
+        return;
+    }
+    BufferWithExtendableBuffer headerBuffer(0 /* originalBuffer */, 0 /* originalBufferSize */);
+    if (!headerPolicy->writeHeaderToBuffer(&headerBuffer, true /* updatesLastUpdatedTime */,
+            mNeedsToDecay, unigramCount, bigramCount, 0 /* extendedRegionSize */)) {
+        return;
+    }
+    DictFileWritingUtils::flushAllHeaderAndBodyToFile(fileName, &headerBuffer, &newDictBuffer);
+}
+
+bool DynamicPatriciaTrieWritingHelper::markNodeAsDeleted(
+        const DynamicPatriciaTrieNodeReader *const nodeToUpdate) {
+    int pos = nodeToUpdate->getHeadPos();
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(pos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    // Read original flags
+    const PatriciaTrieReadingUtils::NodeFlags originalFlags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+            DynamicPatriciaTrieReadingUtils::updateAndGetFlags(originalFlags, false /* isMoved */,
+                    true /* isDeleted */);
+    int writingPos = nodeToUpdate->getHeadPos();
+    // Update flags.
+    return DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+            &writingPos);
+}
+
+bool DynamicPatriciaTrieWritingHelper::markNodeAsMovedAndSetPosition(
+        const DynamicPatriciaTrieNodeReader *const originalNode, const int movedPos,
+        const int bigramLinkedNodePos) {
+    int pos = originalNode->getHeadPos();
+    const bool usesAdditionalBuffer = mBuffer->isInAdditionalBuffer(pos);
+    const uint8_t *const dictBuf = mBuffer->getBuffer(usesAdditionalBuffer);
+    if (usesAdditionalBuffer) {
+        pos -= mBuffer->getOriginalBufferSize();
+    }
+    // Read original flags
+    const PatriciaTrieReadingUtils::NodeFlags originalFlags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictBuf, &pos);
+    const PatriciaTrieReadingUtils::NodeFlags updatedFlags =
+            DynamicPatriciaTrieReadingUtils::updateAndGetFlags(originalFlags, true /* isMoved */,
+                    false /* isDeleted */);
+    int writingPos = originalNode->getHeadPos();
+    // Update flags.
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(mBuffer, updatedFlags,
+            &writingPos)) {
+        return false;
+    }
+    // Update moved position, which is stored in the parent offset field.
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+            mBuffer, movedPos, originalNode->getHeadPos(), &writingPos)) {
+        return false;
+    }
+    // Update bigram linked node position, which is stored in the children position field.
+    int childrenPosFieldPos = originalNode->getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
+            mBuffer, bigramLinkedNodePos, &childrenPosFieldPos)) {
+        return false;
+    }
+    if (originalNode->hasChildren()) {
+        // Update children's parent position.
+        DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
+        const DynamicPatriciaTrieNodeReader *const nodeReader = readingHelper.getNodeReader();
+        readingHelper.initWithPtNodeArrayPos(originalNode->getChildrenPos());
+        while (!readingHelper.isEnd()) {
+            int parentOffsetFieldPos = nodeReader->getHeadPos()
+                    + DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE;
+            if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+                    mBuffer, bigramLinkedNodePos, nodeReader->getHeadPos(),
+                    &parentOffsetFieldPos)) {
+                // Parent offset cannot be written because of a bug or a broken dictionary; thus,
+                // we give up to update dictionary.
+                return false;
+            }
+            readingHelper.readNextSiblingNode();
+        }
+    }
+    return true;
+}
+
+// Write new PtNode at writingPos.
+bool DynamicPatriciaTrieWritingHelper::writePtNodeWithFullInfoToBuffer(
+        BufferWithExtendableBuffer *const bufferToWrite, const bool isBlacklisted,
+        const bool isNotAWord, const int parentPos, const int *const codePoints,
+        const int codePointCount, const int probability, const int childrenPos,
+        const int originalBigramListPos, const int originalShortcutListPos,
+        int *const writingPos) {
+    const int nodePos = *writingPos;
+    // Write dummy flags. The Node flags are updated with appropriate flags at the last step of the
+    // PtNode writing.
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite,
+            0 /* nodeFlags */, writingPos)) {
+        return false;
+    }
+    // Calculate a parent offset and write the offset.
+    if (!DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(bufferToWrite,
+            parentPos, nodePos, writingPos)) {
+        return false;
+    }
+    // Write code points
+    if (!DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(bufferToWrite,
+            codePoints, codePointCount, writingPos)) {
+        return false;
+    }
+    // Write probability when the probability is a valid probability, which means this node is
+    // terminal.
+    if (probability != NOT_A_PROBABILITY) {
+        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(bufferToWrite,
+                probability, writingPos)) {
+            return false;
+        }
+    }
+    // Write children position
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(bufferToWrite,
+            childrenPos, writingPos)) {
+        return false;
+    }
+    // Copy shortcut list when the originalShortcutListPos is valid dictionary position.
+    if (originalShortcutListPos != NOT_A_DICT_POS) {
+        int fromPos = originalShortcutListPos;
+        if (!mShortcutPolicy->copyAllShortcutsAndReturnIfSucceededOrNot(bufferToWrite, &fromPos,
+                writingPos)) {
+            return false;
+        }
+    }
+    // Copy bigram list when the originalBigramListPos is valid dictionary position.
+    int bigramCount = 0;
+    if (originalBigramListPos != NOT_A_DICT_POS) {
+        int fromPos = originalBigramListPos;
+        if (!mBigramPolicy->copyAllBigrams(bufferToWrite, &fromPos, writingPos, &bigramCount)) {
+            return false;
+        }
+    }
+    // Create node flags and write them.
+    PatriciaTrieReadingUtils::NodeFlags nodeFlags =
+            PatriciaTrieReadingUtils::createAndGetFlags(isBlacklisted, isNotAWord,
+                    probability != NOT_A_PROBABILITY /* isTerminal */,
+                    originalShortcutListPos != NOT_A_DICT_POS /* hasShortcutTargets */,
+                    bigramCount > 0 /* hasBigrams */, codePointCount > 1 /* hasMultipleChars */,
+                    CHILDREN_POSITION_FIELD_SIZE);
+    int flagsFieldPos = nodePos;
+    if (!DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(bufferToWrite, nodeFlags,
+            &flagsFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieWritingHelper::writePtNodeToBuffer(
+        BufferWithExtendableBuffer *const bufferToWrite, const int parentPos,
+        const int *const codePoints, const int codePointCount, const int probability,
+        int *const writingPos) {
+    return writePtNodeWithFullInfoToBuffer(bufferToWrite, false /* isBlacklisted */,
+            false /* isNotAWord */, parentPos, codePoints, codePointCount, probability,
+            NOT_A_DICT_POS /* childrenPos */, NOT_A_DICT_POS /* originalBigramsPos */,
+            NOT_A_DICT_POS /* originalShortcutPos */, writingPos);
+}
+
+bool DynamicPatriciaTrieWritingHelper::writePtNodeToBufferByCopyingPtNodeInfo(
+        BufferWithExtendableBuffer *const bufferToWrite,
+        const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
+        const int *const codePoints, const int codePointCount, const int probability,
+        int *const writingPos) {
+    return writePtNodeWithFullInfoToBuffer(bufferToWrite, originalNode->isBlacklisted(),
+            originalNode->isNotAWord(), parentPos, codePoints, codePointCount, probability,
+            originalNode->getChildrenPos(), originalNode->getBigramsPos(),
+            originalNode->getShortcutPos(), writingPos);
+}
+
+bool DynamicPatriciaTrieWritingHelper::createAndInsertNodeIntoPtNodeArray(const int parentPos,
+        const int *const nodeCodePoints, const int nodeCodePointCount, const int probability,
+        int *const forwardLinkFieldPos) {
+    const int newPtNodeArrayPos = mBuffer->getTailPosition();
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            newPtNodeArrayPos, forwardLinkFieldPos)) {
+        return false;
+    }
+    return createNewPtNodeArrayWithAChildPtNode(parentPos, nodeCodePoints, nodeCodePointCount,
+            probability);
+}
+
+bool DynamicPatriciaTrieWritingHelper::setPtNodeProbability(
+        const DynamicPatriciaTrieNodeReader *const originalPtNode, const int probability,
+        const int *const codePoints, bool *const outAddedNewUnigram) {
+    if (originalPtNode->isTerminal()) {
+        // Overwrites the probability.
+        *outAddedNewUnigram = false;
+        const int probabilityToWrite = getUpdatedProbability(originalPtNode->getProbability(),
+                probability);
+        int probabilityFieldPos = originalPtNode->getProbabilityFieldPos();
+        if (!DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(mBuffer,
+                probabilityToWrite, &probabilityFieldPos)) {
+            return false;
+        }
+    } else {
+        // Make the node terminal and write the probability.
+        *outAddedNewUnigram = true;
+        int movedPos = mBuffer->getTailPosition();
+        if (!markNodeAsMovedAndSetPosition(originalPtNode, movedPos, movedPos)) {
+            return false;
+        }
+        if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, originalPtNode,
+                originalPtNode->getParentPos(), codePoints, originalPtNode->getCodePointCount(),
+                getUpdatedProbability(NOT_A_PROBABILITY /* originalProbability */, probability),
+                &movedPos)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieWritingHelper::createChildrenPtNodeArrayAndAChildPtNode(
+        const DynamicPatriciaTrieNodeReader *const parentNode, const int probability,
+        const int *const codePoints, const int codePointCount) {
+    const int newPtNodeArrayPos = mBuffer->getTailPosition();
+    int childrenPosFieldPos = parentNode->getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+            newPtNodeArrayPos, &childrenPosFieldPos)) {
+        return false;
+    }
+    return createNewPtNodeArrayWithAChildPtNode(parentNode->getHeadPos(), codePoints,
+            codePointCount, probability);
+}
+
+bool DynamicPatriciaTrieWritingHelper::createNewPtNodeArrayWithAChildPtNode(
+        const int parentPtNodePos, const int *const nodeCodePoints, const int nodeCodePointCount,
+        const int probability) {
+    int writingPos = mBuffer->getTailPosition();
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
+            1 /* arraySize */, &writingPos)) {
+        return false;
+    }
+    if (!writePtNodeToBuffer(mBuffer, parentPtNodePos, nodeCodePoints, nodeCodePointCount,
+            probability, &writingPos)) {
+        return false;
+    }
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    return true;
+}
+
+// Returns whether the dictionary updating was succeeded or not.
+bool DynamicPatriciaTrieWritingHelper::reallocatePtNodeAndAddNewPtNodes(
+        const DynamicPatriciaTrieNodeReader *const reallocatingPtNode,
+        const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
+        const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
+        const int newNodeCodePointCount) {
+    // When addsExtraChild is true, split the reallocating PtNode and add new child.
+    // Reallocating PtNode: abcde, newNode: abcxy.
+    // abc (1st, not terminal) __ de (2nd)
+    //                         \_ xy (extra child, terminal)
+    // Otherwise, this method makes 1st part terminal and write probabilityOfNewPtNode.
+    // Reallocating PtNode: abcde, newNode: abc.
+    // abc (1st, terminal) __ de (2nd)
+    const bool addsExtraChild = newNodeCodePointCount > overlappingCodePointCount;
+    const int firstPartOfReallocatedPtNodePos = mBuffer->getTailPosition();
+    int writingPos = firstPartOfReallocatedPtNodePos;
+    // Write the 1st part of the reallocating node. The children position will be updated later
+    // with actual children position.
+    const int newProbability = addsExtraChild ? NOT_A_PROBABILITY : probabilityOfNewPtNode;
+    if (!writePtNodeToBuffer(mBuffer, reallocatingPtNode->getParentPos(),
+            reallocatingPtNodeCodePoints, overlappingCodePointCount, newProbability,
+            &writingPos)) {
+        return false;
+    }
+    const int actualChildrenPos = writingPos;
+    // Create new children PtNode array.
+    const size_t newPtNodeCount = addsExtraChild ? 2 : 1;
+    if (!DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(mBuffer,
+            newPtNodeCount, &writingPos)) {
+        return false;
+    }
+    // Write the 2nd part of the reallocating node.
+    const int secondPartOfReallocatedPtNodePos = writingPos;
+    if (!writePtNodeToBufferByCopyingPtNodeInfo(mBuffer, reallocatingPtNode,
+            firstPartOfReallocatedPtNodePos,
+            reallocatingPtNodeCodePoints + overlappingCodePointCount,
+            reallocatingPtNode->getCodePointCount() - overlappingCodePointCount,
+            reallocatingPtNode->getProbability(), &writingPos)) {
+        return false;
+    }
+    if (addsExtraChild) {
+        if (!writePtNodeToBuffer(mBuffer, firstPartOfReallocatedPtNodePos,
+                newNodeCodePoints + overlappingCodePointCount,
+                newNodeCodePointCount - overlappingCodePointCount, probabilityOfNewPtNode,
+                &writingPos)) {
+            return false;
+        }
+    }
+    if (!DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(mBuffer,
+            NOT_A_DICT_POS /* forwardLinkPos */, &writingPos)) {
+        return false;
+    }
+    // Update original reallocatingPtNode as moved.
+    if (!markNodeAsMovedAndSetPosition(reallocatingPtNode, firstPartOfReallocatedPtNodePos,
+            secondPartOfReallocatedPtNodePos)) {
+        return false;
+    }
+    // Load node info. Information of the 1st part will be fetched.
+    DynamicPatriciaTrieNodeReader nodeReader(mBuffer, mBigramPolicy, mShortcutPolicy);
+    nodeReader.fetchNodeInfoInBufferFromPtNodePos(firstPartOfReallocatedPtNodePos);
+    // Update children position.
+    int childrenPosFieldPos = nodeReader.getChildrenPosFieldPos();
+    if (!DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(mBuffer,
+            actualChildrenPos, &childrenPosFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
+bool DynamicPatriciaTrieWritingHelper::runGC(const int rootPtNodeArrayPos,
+        const HeaderPolicy *const headerPolicy, BufferWithExtendableBuffer *const bufferToWrite,
+        int *const outUnigramCount, int *const outBigramCount) {
+    DynamicPatriciaTrieReadingHelper readingHelper(mBuffer, mBigramPolicy, mShortcutPolicy);
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners
+            ::TraversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+                    traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted(
+                            headerPolicy, this, mBuffer, mNeedsToDecay);
+    if (!readingHelper.traverseAllPtNodesInPostorderDepthFirstManner(
+            &traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted)) {
+        return false;
+    }
+    if (mNeedsToDecay && traversePolicyToUpdateUnigramProbabilityAndMarkUselessPtNodesAsDeleted
+            .getValidUnigramCount() > ForgettingCurveUtils::MAX_UNIGRAM_COUNT_AFTER_GC) {
+        // TODO: Remove more unigrams.
+    }
+
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateBigramProbability
+            traversePolicyToUpdateBigramProbability(mBigramPolicy);
+    if (!readingHelper.traverseAllPtNodesInPostorderDepthFirstManner(
+            &traversePolicyToUpdateBigramProbability)) {
+        return false;
+    }
+    if (mNeedsToDecay && traversePolicyToUpdateBigramProbability.getValidBigramEntryCount()
+            > ForgettingCurveUtils::MAX_BIGRAM_COUNT_AFTER_GC) {
+        // TODO: Remove more bigrams.
+    }
+
+    // Mapping from positions in mBuffer to positions in bufferToWrite.
+    DictPositionRelocationMap dictPositionRelocationMap;
+    readingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToPlaceAndWriteValidPtNodesToBuffer
+            traversePolicyToPlaceAndWriteValidPtNodesToBuffer(this, bufferToWrite,
+                    &dictPositionRelocationMap);
+    if (!readingHelper.traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            &traversePolicyToPlaceAndWriteValidPtNodesToBuffer)) {
+        return false;
+    }
+
+    // Create policy instance for the GCed dictionary.
+    DynamicShortcutListPolicy newDictShortcutPolicy(bufferToWrite);
+    DynamicBigramListPolicy newDictBigramPolicy(headerPolicy, bufferToWrite, &newDictShortcutPolicy,
+            mNeedsToDecay);
+    // Create reading helper for the GCed dictionary.
+    DynamicPatriciaTrieReadingHelper newDictReadingHelper(bufferToWrite, &newDictBigramPolicy,
+            &newDictShortcutPolicy);
+    newDictReadingHelper.initWithPtNodeArrayPos(rootPtNodeArrayPos);
+    DynamicPatriciaTrieGcEventListeners::TraversePolicyToUpdateAllPositionFields
+            traversePolicyToUpdateAllPositionFields(this, &newDictBigramPolicy, bufferToWrite,
+                    &dictPositionRelocationMap);
+    if (!newDictReadingHelper.traverseAllPtNodesInPtNodeArrayLevelPreorderDepthFirstManner(
+            &traversePolicyToUpdateAllPositionFields)) {
+        return false;
+    }
+    *outUnigramCount = traversePolicyToUpdateAllPositionFields.getUnigramCount();
+    *outBigramCount = traversePolicyToUpdateAllPositionFields.getBigramCount();
+    return true;
+}
+
+int DynamicPatriciaTrieWritingHelper::getUpdatedProbability(const int originalProbability,
+        const int newProbability) {
+    if (mNeedsToDecay) {
+        return ForgettingCurveUtils::getUpdatedEncodedProbability(originalProbability,
+                newProbability);
+    } else {
+        return newProbability;
+    }
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
new file mode 100644
index 0000000..ca86647
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_helper.h
@@ -0,0 +1,138 @@
+/*
+ * 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_WRITING_HELPER_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+class DynamicBigramListPolicy;
+class DynamicPatriciaTrieNodeReader;
+class DynamicPatriciaTrieReadingHelper;
+class DynamicShortcutListPolicy;
+class HeaderPolicy;
+
+class DynamicPatriciaTrieWritingHelper {
+ public:
+    typedef hash_map_compat<int, int> PtNodeArrayPositionRelocationMap;
+    typedef hash_map_compat<int, int> PtNodePositionRelocationMap;
+    struct DictPositionRelocationMap {
+     public:
+        DictPositionRelocationMap()
+                : mPtNodeArrayPositionRelocationMap(), mPtNodePositionRelocationMap() {}
+
+        PtNodeArrayPositionRelocationMap mPtNodeArrayPositionRelocationMap;
+        PtNodePositionRelocationMap mPtNodePositionRelocationMap;
+
+     private:
+        DISALLOW_COPY_AND_ASSIGN(DictPositionRelocationMap);
+    };
+
+    static const size_t MAX_DICTIONARY_SIZE;
+
+    DynamicPatriciaTrieWritingHelper(BufferWithExtendableBuffer *const buffer,
+            DynamicBigramListPolicy *const bigramPolicy,
+            DynamicShortcutListPolicy *const shortcutPolicy, const bool needsToDecay)
+            : mBuffer(buffer), mBigramPolicy(bigramPolicy), mShortcutPolicy(shortcutPolicy),
+              mNeedsToDecay(needsToDecay) {}
+
+    ~DynamicPatriciaTrieWritingHelper() {}
+
+    // Add a word to the dictionary. If the word already exists, update the probability.
+    bool addUnigramWord(DynamicPatriciaTrieReadingHelper *const readingHelper,
+            const int *const wordCodePoints, const int codePointCount, const int probability,
+            bool *const outAddedNewUnigram);
+
+    // Add a bigram relation from word0Pos to word1Pos.
+    bool addBigramWords(const int word0Pos, const int word1Pos, const int probability,
+            bool *const outAddedNewBigram);
+
+    // Remove a bigram relation from word0Pos to word1Pos.
+    bool removeBigramWords(const int word0Pos, const int word1Pos);
+
+    void writeToDictFile(const char *const fileName, const HeaderPolicy *const headerPolicy,
+            const int unigramCount, const int bigramCount);
+
+    void writeToDictFileWithGC(const int rootPtNodeArrayPos, const char *const fileName,
+            const HeaderPolicy *const headerPolicy);
+
+    // CAVEAT: This method must be called only from inner classes of
+    // DynamicPatriciaTrieGcEventListeners.
+    bool markNodeAsDeleted(const DynamicPatriciaTrieNodeReader *const nodeToUpdate);
+
+    // CAVEAT: This method must be called only from this class or inner classes of
+    // DynamicPatriciaTrieGcEventListeners.
+    bool writePtNodeToBufferByCopyingPtNodeInfo(BufferWithExtendableBuffer *const bufferToWrite,
+            const DynamicPatriciaTrieNodeReader *const originalNode, const int parentPos,
+            const int *const codePoints, const int codePointCount, const int probability,
+            int *const writingPos);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingHelper);
+
+    static const int CHILDREN_POSITION_FIELD_SIZE;
+
+    BufferWithExtendableBuffer *const mBuffer;
+    DynamicBigramListPolicy *const mBigramPolicy;
+    DynamicShortcutListPolicy *const mShortcutPolicy;
+    const bool mNeedsToDecay;
+
+    bool markNodeAsMovedAndSetPosition(const DynamicPatriciaTrieNodeReader *const nodeToUpdate,
+            const int movedPos, const int bigramLinkedNodePos);
+
+    bool writePtNodeWithFullInfoToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+            const bool isBlacklisted, const bool isNotAWord,
+            const int parentPos,  const int *const codePoints, const int codePointCount,
+            const int probability, const int childrenPos, const int originalBigramListPos,
+            const int originalShortcutListPos, int *const writingPos);
+
+    bool writePtNodeToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+            const int parentPos, const int *const codePoints, const int codePointCount,
+            const int probability, int *const writingPos);
+
+    bool createAndInsertNodeIntoPtNodeArray(const int parentPos, const int *const nodeCodePoints,
+            const int nodeCodePointCount, const int probability, int *const forwardLinkFieldPos);
+
+    bool setPtNodeProbability(const DynamicPatriciaTrieNodeReader *const originalNode,
+            const int probability, const int *const codePoints, bool *const outAddedNewUnigram);
+
+    bool createChildrenPtNodeArrayAndAChildPtNode(
+            const DynamicPatriciaTrieNodeReader *const parentNode, const int probability,
+            const int *const codePoints, const int codePointCount);
+
+    bool createNewPtNodeArrayWithAChildPtNode(const int parentPos, const int *const nodeCodePoints,
+            const int nodeCodePointCount, const int probability);
+
+    bool reallocatePtNodeAndAddNewPtNodes(
+            const DynamicPatriciaTrieNodeReader *const reallocatingPtNode,
+            const int *const reallocatingPtNodeCodePoints, const int overlappingCodePointCount,
+            const int probabilityOfNewPtNode, const int *const newNodeCodePoints,
+            const int newNodeCodePointCount);
+
+    bool runGC(const int rootPtNodeArrayPos, const HeaderPolicy *const headerPolicy,
+            BufferWithExtendableBuffer *const bufferToWrite, int *const outUnigramCount,
+            int *const outBigramCount);
+
+    int getUpdatedProbability(const int originalProbability, const int newProbability);
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_HELPER_H */
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
new file mode 100644
index 0000000..30ff10c
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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_writing_utils.h"
+
+#include <cstddef>
+#include <cstdlib>
+#include <stdint.h>
+
+#include "suggest/policyimpl/dictionary/utils/buffer_with_extendable_buffer.h"
+
+namespace latinime {
+
+const size_t DynamicPatriciaTrieWritingUtils::MAX_PTNODE_ARRAY_SIZE_TO_USE_SMALL_SIZE_FIELD = 0x7F;
+const size_t DynamicPatriciaTrieWritingUtils::MAX_PTNODE_ARRAY_SIZE = 0x7FFF;
+const int DynamicPatriciaTrieWritingUtils::SMALL_PTNODE_ARRAY_SIZE_FIELD_SIZE = 1;
+const int DynamicPatriciaTrieWritingUtils::LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE = 2;
+const int DynamicPatriciaTrieWritingUtils::LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG = 0x8000;
+const int DynamicPatriciaTrieWritingUtils::DICT_OFFSET_FIELD_SIZE = 3;
+const int DynamicPatriciaTrieWritingUtils::MAX_DICT_OFFSET_VALUE = 0x7FFFFF;
+const int DynamicPatriciaTrieWritingUtils::MIN_DICT_OFFSET_VALUE = -0x7FFFFF;
+const int DynamicPatriciaTrieWritingUtils::DICT_OFFSET_NEGATIVE_FLAG = 0x800000;
+const int DynamicPatriciaTrieWritingUtils::PROBABILITY_FIELD_SIZE = 1;
+const int DynamicPatriciaTrieWritingUtils::NODE_FLAG_FIELD_SIZE = 1;
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeEmptyDictionary(
+        BufferWithExtendableBuffer *const buffer, const int rootPos) {
+    int writingPos = rootPos;
+    if (!writePtNodeArraySizeAndAdvancePosition(buffer, 0 /* arraySize */, &writingPos)) {
+        return false;
+    }
+    return writeForwardLinkPositionAndAdvancePosition(buffer, NOT_A_DICT_POS /* forwardLinkPos */,
+            &writingPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeForwardLinkPositionAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
+        int *const forwardLinkFieldPos) {
+    return writeDictOffset(buffer, forwardLinkPos, (*forwardLinkFieldPos), forwardLinkFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writePtNodeArraySizeAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const size_t arraySize,
+        int *const arraySizeFieldPos) {
+    // Currently, all array size field to be created has LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE to
+    // simplify updating process.
+    // TODO: Use SMALL_PTNODE_ARRAY_SIZE_FIELD_SIZE for small arrays.
+    /*if (arraySize <= MAX_PTNODE_ARRAY_SIZE_TO_USE_SMALL_SIZE_FIELD) {
+        return buffer->writeUintAndAdvancePosition(arraySize, SMALL_PTNODE_ARRAY_SIZE_FIELD_SIZE,
+                arraySizeFieldPos);
+    } else */
+    if (arraySize <= MAX_PTNODE_ARRAY_SIZE) {
+        uint32_t data = arraySize | LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG;
+        return buffer->writeUintAndAdvancePosition(data, LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE,
+                arraySizeFieldPos);
+    } else {
+        AKLOGI("PtNode array size cannot be written because arraySize is too large: %zd",
+                arraySize);
+        ASSERT(false);
+        return false;
+    }
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeFlagsAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer,
+        const DynamicPatriciaTrieReadingUtils::NodeFlags nodeFlags, int *const nodeFlagsFieldPos) {
+    return buffer->writeUintAndAdvancePosition(nodeFlags, NODE_FLAG_FIELD_SIZE, nodeFlagsFieldPos);
+}
+
+// Note that parentOffset is offset from node's head position.
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeParentPosOffsetAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int parentPos, const int basePos,
+        int *const parentPosFieldPos) {
+    return writeDictOffset(buffer, parentPos, basePos, parentPosFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeCodePointsAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int *const codePoints,
+        const int codePointCount, int *const codePointFieldPos) {
+    if (codePointCount <= 0) {
+        AKLOGI("code points cannot be written because codePointCount is invalid: %d",
+                codePointCount);
+        ASSERT(false);
+        return false;
+    }
+    const bool hasMultipleCodePoints = codePointCount > 1;
+    return buffer->writeCodePointsAndAdvancePosition(codePoints, codePointCount,
+            hasMultipleCodePoints, codePointFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeProbabilityAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int probability,
+        int *const probabilityFieldPos) {
+    if (probability < 0 || probability > MAX_PROBABILITY) {
+        AKLOGI("probability cannot be written because the probability is invalid: %d",
+                probability);
+        ASSERT(false);
+        return false;
+    }
+    return buffer->writeUintAndAdvancePosition(probability, PROBABILITY_FIELD_SIZE,
+            probabilityFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeChildrenPositionAndAdvancePosition(
+        BufferWithExtendableBuffer *const buffer, const int childrenPosition,
+        int *const childrenPositionFieldPos) {
+    return writeDictOffset(buffer, childrenPosition, (*childrenPositionFieldPos),
+            childrenPositionFieldPos);
+}
+
+/* static */ bool DynamicPatriciaTrieWritingUtils::writeDictOffset(
+        BufferWithExtendableBuffer *const buffer, const int targetPos, const int basePos,
+        int *const offsetFieldPos) {
+    int offset = targetPos - basePos;
+    if (targetPos == NOT_A_DICT_POS) {
+        offset = DynamicPatriciaTrieReadingUtils::DICT_OFFSET_INVALID;
+    } else if (offset == 0) {
+        offset = DynamicPatriciaTrieReadingUtils::DICT_OFFSET_ZERO_OFFSET;
+    }
+    if (offset > MAX_DICT_OFFSET_VALUE || offset < MIN_DICT_OFFSET_VALUE) {
+        AKLOGI("offset cannot be written because the offset is too large or too small: %d",
+                offset);
+        ASSERT(false);
+        return false;
+    }
+    uint32_t data = 0;
+    if (offset >= 0) {
+        data = offset;
+    } else {
+        data = abs(offset) | DICT_OFFSET_NEGATIVE_FLAG;
+    }
+    return buffer->writeUintAndAdvancePosition(data, DICT_OFFSET_FIELD_SIZE, offsetFieldPos);
+}
+}
diff --git a/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
new file mode 100644
index 0000000..af76bc6
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/dynamic_patricia_trie_writing_utils.h
@@ -0,0 +1,76 @@
+/*
+ * 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_WRITING_UTILS_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_UTILS_H
+
+#include <cstddef>
+
+#include "defines.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+
+namespace latinime {
+
+class BufferWithExtendableBuffer;
+
+class DynamicPatriciaTrieWritingUtils {
+ public:
+    static const int NODE_FLAG_FIELD_SIZE;
+
+    static bool writeEmptyDictionary(BufferWithExtendableBuffer *const buffer, const int rootPos);
+
+    static bool writeForwardLinkPositionAndAdvancePosition(
+            BufferWithExtendableBuffer *const buffer, const int forwardLinkPos,
+            int *const forwardLinkFieldPos);
+
+    static bool writePtNodeArraySizeAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const size_t arraySize, int *const arraySizeFieldPos);
+
+    static bool writeFlagsAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const DynamicPatriciaTrieReadingUtils::NodeFlags nodeFlags,
+            int *const nodeFlagsFieldPos);
+
+    static bool writeParentPosOffsetAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const int parentPosition, const int basePos, int *const parentPosFieldPos);
+
+    static bool writeCodePointsAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const int *const codePoints, const int codePointCount, int *const codePointFieldPos);
+
+    static bool writeProbabilityAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const int probability, int *const probabilityFieldPos);
+
+    static bool writeChildrenPositionAndAdvancePosition(BufferWithExtendableBuffer *const buffer,
+            const int childrenPosition, int *const childrenPositionFieldPos);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieWritingUtils);
+
+    static const size_t MAX_PTNODE_ARRAY_SIZE_TO_USE_SMALL_SIZE_FIELD;
+    static const size_t MAX_PTNODE_ARRAY_SIZE;
+    static const int SMALL_PTNODE_ARRAY_SIZE_FIELD_SIZE;
+    static const int LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE;
+    static const int LARGE_PTNODE_ARRAY_SIZE_FIELD_SIZE_FLAG;
+    static const int DICT_OFFSET_FIELD_SIZE;
+    static const int MAX_DICT_OFFSET_VALUE;
+    static const int MIN_DICT_OFFSET_VALUE;
+    static const int DICT_OFFSET_NEGATIVE_FLAG;
+    static const int PROBABILITY_FIELD_SIZE;
+
+    static bool writeDictOffset(BufferWithExtendableBuffer *const buffer, const int targetPos,
+            const int basePos, int *const offsetFieldPos);
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_WRITING_UTILS_H */
diff --git a/src/aosp/suggest/policyimpl/dictionary/header/header_policy.cpp b/src/aosp/suggest/policyimpl/dictionary/header/header_policy.cpp
new file mode 100644
index 0000000..eb072fb
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/header/header_policy.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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/header/header_policy.h"
+
+namespace latinime {
+
+// Note that these are corresponding definitions in Java side in FormatSpec.FileHeader.
+const char *const HeaderPolicy::MULTIPLE_WORDS_DEMOTION_RATE_KEY = "MULTIPLE_WORDS_DEMOTION_RATE";
+// TODO: Change attribute string to "IS_DECAYING_DICT".
+const char *const HeaderPolicy::IS_DECAYING_DICT_KEY = "USES_FORGETTING_CURVE";
+const char *const HeaderPolicy::LAST_UPDATED_TIME_KEY = "date";
+const char *const HeaderPolicy::LAST_DECAYED_TIME_KEY = "LAST_DECAYED_TIME";
+const char *const HeaderPolicy::UNIGRAM_COUNT_KEY = "UNIGRAM_COUNT";
+const char *const HeaderPolicy::BIGRAM_COUNT_KEY = "BIGRAM_COUNT";
+const char *const HeaderPolicy::EXTENDED_REGION_SIZE_KEY = "EXTENDED_REGION_SIZE";
+const int HeaderPolicy::DEFAULT_MULTIPLE_WORDS_DEMOTION_RATE = 100;
+const float HeaderPolicy::MULTIPLE_WORD_COST_MULTIPLIER_SCALE = 100.0f;
+
+// Used for logging. Question mark is used to indicate that the key is not found.
+void HeaderPolicy::readHeaderValueOrQuestionMark(const char *const key, int *outValue,
+        int outValueSize) const {
+    if (outValueSize <= 0) return;
+    if (outValueSize == 1) {
+        outValue[0] = '\0';
+        return;
+    }
+    std::vector<int> keyCodePointVector;
+    HeaderReadWriteUtils::insertCharactersIntoVector(key, &keyCodePointVector);
+    HeaderReadWriteUtils::AttributeMap::const_iterator it = mAttributeMap.find(keyCodePointVector);
+    if (it == mAttributeMap.end()) {
+        // The key was not found.
+        outValue[0] = '?';
+        outValue[1] = '\0';
+        return;
+    }
+    const int terminalIndex = min(static_cast<int>(it->second.size()), outValueSize - 1);
+    for (int i = 0; i < terminalIndex; ++i) {
+        outValue[i] = it->second[i];
+    }
+    outValue[terminalIndex] = '\0';
+}
+
+float HeaderPolicy::readMultipleWordCostMultiplier() const {
+    const int demotionRate = HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
+            MULTIPLE_WORDS_DEMOTION_RATE_KEY, DEFAULT_MULTIPLE_WORDS_DEMOTION_RATE);
+    if (demotionRate <= 0) {
+        return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+    }
+    return MULTIPLE_WORD_COST_MULTIPLIER_SCALE / static_cast<float>(demotionRate);
+}
+
+bool HeaderPolicy::writeHeaderToBuffer(BufferWithExtendableBuffer *const bufferToWrite,
+        const bool updatesLastUpdatedTime, const bool updatesLastDecayedTime,
+        const int unigramCount, const int bigramCount, const int extendedRegionSize) const {
+    int writingPos = 0;
+    if (!HeaderReadWriteUtils::writeDictionaryVersion(bufferToWrite, mDictFormatVersion,
+            &writingPos)) {
+        return false;
+    }
+    if (!HeaderReadWriteUtils::writeDictionaryFlags(bufferToWrite, mDictionaryFlags,
+            &writingPos)) {
+        return false;
+    }
+    // Temporarily writes a dummy header size.
+    int headerSizeFieldPos = writingPos;
+    if (!HeaderReadWriteUtils::writeDictionaryHeaderSize(bufferToWrite, 0 /* size */,
+            &writingPos)) {
+        return false;
+    }
+    HeaderReadWriteUtils::AttributeMap attributeMapTowrite(mAttributeMap);
+    HeaderReadWriteUtils::setIntAttribute(&attributeMapTowrite, UNIGRAM_COUNT_KEY, unigramCount);
+    HeaderReadWriteUtils::setIntAttribute(&attributeMapTowrite, BIGRAM_COUNT_KEY, bigramCount);
+    HeaderReadWriteUtils::setIntAttribute(&attributeMapTowrite, EXTENDED_REGION_SIZE_KEY,
+            extendedRegionSize);
+    if (updatesLastUpdatedTime) {
+        // Set current time as a last updated time.
+        HeaderReadWriteUtils::setIntAttribute(&attributeMapTowrite, LAST_UPDATED_TIME_KEY,
+                time(0));
+    }
+    if (updatesLastDecayedTime) {
+        // Set current time as a last updated time.
+        HeaderReadWriteUtils::setIntAttribute(&attributeMapTowrite, LAST_DECAYED_TIME_KEY,
+                time(0));
+    }
+    if (!HeaderReadWriteUtils::writeHeaderAttributes(bufferToWrite, &attributeMapTowrite,
+            &writingPos)) {
+        return false;
+    }
+    // Writes an actual header size.
+    if (!HeaderReadWriteUtils::writeDictionaryHeaderSize(bufferToWrite, writingPos,
+            &headerSizeFieldPos)) {
+        return false;
+    }
+    return true;
+}
+
+/* static */ HeaderReadWriteUtils::AttributeMap
+        HeaderPolicy::createAttributeMapAndReadAllAttributes(const uint8_t *const dictBuf) {
+    HeaderReadWriteUtils::AttributeMap attributeMap;
+    HeaderReadWriteUtils::fetchAllHeaderAttributes(dictBuf, &attributeMap);
+    return attributeMap;
+}
+
+} // namespace latinime
diff --git a/src/aosp/suggest/policyimpl/dictionary/header/header_policy.h b/src/aosp/suggest/policyimpl/dictionary/header/header_policy.h
new file mode 100644
index 0000000..a9c7805
--- /dev/null
+++ b/src/aosp/suggest/policyimpl/dictionary/header/header_policy.h
@@ -0,0 +1,151 @@
+/*
+ * 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_HEADER_POLICY_H
+#define LATINIME_HEADER_POLICY_H
+
+#include <ctime>
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_header_structure_policy.h"
+#include "suggest/policyimpl/dictionary/header/header_read_write_utils.h"
+#include "suggest/policyimpl/dictionary/utils/format_utils.h"
+
+namespace latinime {
+
+class HeaderPolicy : public DictionaryHeaderStructurePolicy {
+ public:
+    // Reads information from existing dictionary buffer.
+    HeaderPolicy(const uint8_t *const dictBuf, const int dictSize)
+            : mDictFormatVersion(FormatUtils::detectFormatVersion(dictBuf, dictSize)),
+              mDictionaryFlags(HeaderReadWriteUtils::getFlags(dictBuf)),
+              mSize(HeaderReadWriteUtils::getHeaderSize(dictBuf)),
+              mAttributeMap(createAttributeMapAndReadAllAttributes(dictBuf)),
+              mMultiWordCostMultiplier(readMultipleWordCostMultiplier()),
+              mIsDecayingDict(HeaderReadWriteUtils::readBoolAttributeValue(&mAttributeMap,
+                      IS_DECAYING_DICT_KEY, false /* defaultValue */)),
+              mLastUpdatedTime(HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
+                      LAST_UPDATED_TIME_KEY, time(0) /* defaultValue */)),
+              mLastDecayedTime(HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
+                      LAST_DECAYED_TIME_KEY, time(0) /* defaultValue */)),
+              mUnigramCount(HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
+                      UNIGRAM_COUNT_KEY, 0 /* defaultValue */)),
+              mBigramCount(HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
+                      BIGRAM_COUNT_KEY, 0 /* defaultValue */)),
+              mExtendedRegionSize(HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
+                      EXTENDED_REGION_SIZE_KEY, 0 /* defaultValue */)) {}
+
+    // Constructs header information using an attribute map.
+    HeaderPolicy(const FormatUtils::FORMAT_VERSION dictFormatVersion,
+            const HeaderReadWriteUtils::AttributeMap *const attributeMap)
+            : mDictFormatVersion(dictFormatVersion),
+              mDictionaryFlags(HeaderReadWriteUtils::createAndGetDictionaryFlagsUsingAttributeMap(
+                      attributeMap)), mSize(0), mAttributeMap(*attributeMap),
+              mMultiWordCostMultiplier(readMultipleWordCostMultiplier()),
+              mIsDecayingDict(HeaderReadWriteUtils::readBoolAttributeValue(&mAttributeMap,
+                      IS_DECAYING_DICT_KEY, false /* defaultValue */)),
+              mLastUpdatedTime(HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
+                      LAST_UPDATED_TIME_KEY, time(0) /* defaultValue */)),
+              mLastDecayedTime(HeaderReadWriteUtils::readIntAttributeValue(&mAttributeMap,
+                      LAST_UPDATED_TIME_KEY, time(0) /* defaultValue */)),
+              mUnigramCount(0), mBigramCount(0), mExtendedRegionSize(0) {}
+
+    ~HeaderPolicy() {}
+
+    AK_FORCE_INLINE int getSize() const {
+        return mSize;
+    }
+
+    AK_FORCE_INLINE bool supportsDynamicUpdate() const {
+        return HeaderReadWriteUtils::supportsDynamicUpdate(mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE bool requiresGermanUmlautProcessing() const {
+        return HeaderReadWriteUtils::requiresGermanUmlautProcessing(mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE bool requiresFrenchLigatureProcessing() const {
+        return HeaderReadWriteUtils::requiresFrenchLigatureProcessing(mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE float getMultiWordCostMultiplier() const {
+        return mMultiWordCostMultiplier;
+    }
+
+    AK_FORCE_INLINE bool isDecayingDict() const {
+        return mIsDecayingDict;
+    }
+
+    AK_FORCE_INLINE int getLastUpdatedTime() const {
+        return mLastUpdatedTime;
+    }
+
+    AK_FORCE_INLINE int getLastDecayedTime() const {
+        return mLastDecayedTime;
+    }
+
+    AK_FORCE_INLINE int getUnigramCount() const {
+        return mUnigramCount;
+    }
+
+    AK_FORCE_INLINE int getBigramCount() const {
+        return mBigramCount;
+    }
+
+    AK_FORCE_INLINE int getExtendedRegionSize() const {
+        return mExtendedRegionSize;
+    }
+
+    void readHeaderValueOrQuestionMark(const char *const key,
+