Merge "Avoid NPE"
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 80cd085..17d11c0 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -95,6 +95,12 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name=".DictionaryPackInstallBroadcastReceiver">
+            <intent-filter>
+                <action android:name="com.android.inputmethod.dictionarypack.UNKNOWN_CLIENT" />
+            </intent-filter>
+        </receiver>
+
         <provider android:name="com.android.inputmethod.dictionarypack.DictionaryProvider"
                   android:grantUriPermissions="true"
                   android:exported="false"
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index f0b47be..2892940 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android-sleutelbord (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android-sleutelbord-instellings (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android-speltoetser (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android-speltoetserinstellings (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropsies"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Navorsing-loglêerbevele"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Soek kontakname op"</string>
@@ -139,7 +135,7 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Raak weer om te stoor"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Woordeboek beskikbaar"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktiveer gebruikerterugvoer"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Help hierdie invoermetode-redigeerder te verbeter deur gebruikstatistiek en omvalverslae outomaties na Google te stuur."</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Help om hierdie invoermetode-redigeerder te verbeter deur gebruikstatistiek en omvalverslae outomaties te stuur"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Sleutelbordtema"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engels (VK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engels (VS)"</string>
@@ -166,9 +162,9 @@
     <string name="not_now" msgid="6172462888202790482">"Nie nou nie"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Dieselfde invoerstyl bestaan ​​reeds: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Bruikbaarheidstudie-modus"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Sleutellangdrukvertraging-instellings"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Sleuteldruk se vibrasie-tydsduurinstellings"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Sleuteldruk se klankvolume-instellings"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Vertraging van sleutellangdruk"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Sleuteldruk se vibrasie-tydsduur"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Sleuteldruk se klankvolume"</string>
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Lees eksterne woordeboeklêer"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Geen woordeboeklêers in die aflaaiselsvouer nie"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Kies \'n woordeboeklêer om te installeer"</string>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index f6ccec7..23add87 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"የAndroid ቁልፍ ሰሌዳ (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"የAndroid ቁልፍ ሰሌዳ ቅንብሮች (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android ፊደል አራሚ (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"የAndroid ፊደል አራሚ ቅንብሮች (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"ግቤት አማራጮች"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"የጥናት የምዝግብ ማስታወሻ ትዕዛዞች"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"የእውቅያ ስሞችን ተመልከት"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"ለማስቀመጥ እንደገና ንካ"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"መዝገበ ቃላት አለ"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"የተጠቃሚ ግብረ ምላሽ አንቃ"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"ወደ Google የተሰናከለ ሪፖርቶች እና አጠቃቀም ስታስቲክስ በራስ ሰር በመላክ ይህን ግቤት ሜተድ አርትኢ እገዛ ያሻሽላል።"</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"የቁልፍ ሰሌዳ ገጽታ"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"እንግሊዘኛ (የታላቋ ብሪታንያ)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"እንግሊዘኛ (ዩ.ኤስ)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"አሁን አልፈልግም"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"ተመሳሳዩ የግብዓት ቅጥ አስቀድሞ አለ፦ <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"የተገልጋይነት ጥናት ሁነታ"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"የቁልፍ ረጅም ጭነት መዘግየት ቅንብሮች"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"ቁልፍ ተጫን በቅንጅቶች ወቅት ንዝረት"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"ቁልፍ ተጫን የድምጽ መጠን ቅንብሮች"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"ውጫዊ የመዝገበቃላት ፋይል አንብብ"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"በውርዶች አቃፊው ውስጥ ምንም የመዝገበ-ፋይሎች የሉም"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"የሚጭኑት የመዝገበ-ቃላት ፋይል ይምረጡ"</string>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index f9b01d3..f364bcf 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"لوحة مفاتيح Android ‏(AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"إعدادات لوحة مفاتيح Android‏ (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"المدقق الإملائي في Android‏ (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"إعدادات المدقق الإملائي في Android‏ (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"خيارات الإرسال"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"أوامر سجلات البحث"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"بحث في أسماء جهات الاتصال"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"المس مرة أخرى للحفظ"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"القاموس متاح"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"تمكين ملاحظات المستخدم"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"المساعدة في تحسين محرر طريقة الإرسال هذا من خلال إرسال إحصاءات الاستخدام وتقارير الأعطال تلقائيًا إلى Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"مظهر لوحة المفاتيح"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"الإنجليزية (المملكة المتحدة)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"الإنجليزية (الولايات المتحدة)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"ليس الآن"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"نمط الإدخال ذاته موجود من قبل: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"وضع سهولة الاستخدام"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"إعدادات تأخير الضغط الطويل للمفاتيح"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"إعدادات مدة اهتزاز الضغط على المفاتيح"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"إعدادات مستوى صوت الضغط على المفاتيح"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"قراءة ملف قاموس خارجي"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"ليست هناك ملفات قواميس في مجلد التنزيلات"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"تحديد ملف قاموس للتثبيت"</string>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index 6c95cce..2d3fcfb 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Клавіятура Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Налады клавіятуры Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Iнструмент праверкi правапiсу для Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Налады інструмента праверкі правапісу для Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Параметры ўводу"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Каманды гiсторыя даследаванняў"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Шукаць імёны кантактаў"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Дакраніцеся зноў, каб захаваць"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Слоўнік даступны"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Уключыць зваротную сувязь з карыстальнікамі"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Дапамажыце палепшыць гэты рэдактар ​​метаду ўводу, аўтаматычна адпраўляючы статыстыку выкарыстання і справаздачы аб збоях Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Тэма клавіятуры"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Англійская (ЗК)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Англійская (ЗША)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Не цяпер"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Такі метад уводу ўжо існуе: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Рэжым даследвання выкарыстальнасці"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Налады адмены доўгага нацiску клавiшы"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Налады працягласцi вiбрацыi пры нацiску"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Налады гучнасцi пры нацiску"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Чытанне знешняга файла слоўніка"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"У папцы загрузак няма файлаў слоўніка"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Вылучыце файл слоўніка для ўсталёўкі"</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 58a6a70..4862198 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Клавиатура на Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Настройки на клавиатурата на Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Програма за правописна проверка за Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Настройки на програмата за правописна проверка за Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опции за въвеждане"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Команди за рег. файл за проучвания"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Търсене на имена"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Докоснете отново, за да запазите"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Има достъп до речник"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Активиране на отзивите от потребителите"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Помогнете за подобряването на този редактор за въвеждане чрез автоматично изпращане до Google на статистически данни за употребата и сигнали за сривове."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема на клавиатурата"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"английски (Великобритания)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"английски (САЩ)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Не сега"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Същият стил на въвеждане вече съществува: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим за изучаване на използваемостта"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Настройки за забавяне при продължително натискане на клавишите"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Настройки за продължителност на вибрирането при натискане на клавиш"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Настройки за силата на звука при натискане на клавиш"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Четене на файл за външен речник"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"В папката „Изтегляния“ няма файлове за речник"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Изберете файл за речника, който да инсталирате"</string>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index 5539fee..93336c6 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Teclat d\'Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Configuració del teclat d\'Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Corrector ortogràfic d\'Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Configuració del corrector ortogràfic d\'Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcions d\'entrada"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Recerca d\'ordres de reg."</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cerca noms de contactes"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Torna a tocar per desar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Diccionari disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activa els comentaris de l\'usuari"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Ajuda a millorar aquest editor de mètodes d\'entrada enviant automàticament estadístiques d\'ús i informes de bloqueigs a Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema del teclat"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglès (Regne Unit)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglès (EUA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ara no"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Ja existeix aquest estil d\'entrada: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mode d\'estudi d\'usabilitat"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Configuració del retard per mantenir premuda una tecla"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Configuració de durada de vibracions en prémer tecles"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Configuració del volum de so en prémer tecles"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Lectura d\'un fitxer de diccionari extern"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"No hi ha cap fitxer de diccionari a la carpeta Baixades"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Selecció d\'un fitxer de diccionari per instal·lar"</string>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index c7261ba..355d681 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Klávesnice Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Nastavení klávesnice Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Kontrola pravopisu Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Nastavení kontroly pravopisu Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávání textu a dat"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Příkazy vývoj. protokolu"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Vyhledat kontakty"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Opětovným dotykem provedete uložení"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Slovník k dispozici"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktivovat zasílání statistik užívání a zpráv o selhání"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Automatickým zasíláním statistik o užívání editoru zadávání dat a zpráv o jeho selhání do Googlu můžete přispět k vylepšení tohoto nástroje."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Motiv klávesnice"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"angličtina (Velká Británie)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"angličtina (USA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Teď ne"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Tento styl zadávání již existuje: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Režim studie použitelnosti"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Nastavení prodlevy dlouhého stisknutí kláves"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Délka vibrace při stisku klávesy"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Hlasitost při stisknutí klávesy"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Číst soubor externího slovníku"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Ve složce Stažené nejsou žádné soubory slovníků."</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Vyberte soubor slovníku k instalaci"</string>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 1ed9983..8b4ea07 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android-tastatur (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Indstillinger for Android-tastatur (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android-stavekontrol (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Indstillinger for Android-stavekontrol (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Indstillinger for input"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Forskningslogkommandoer"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Slå kontaktnavne op"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Tryk igen for at gemme"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ordbog er tilgængelig"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktivér brugerfeedback"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Vær med til at forbedre denne inputmetode ved at sende anvendelsesstatistikker og rapporter om nedbrud til Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tastaturtema"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engelsk (Storbritannien)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engelsk (USA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ikke nu"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Denne inputstil findes allerede: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Tilstand for brugsstudie"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Forsinkelsesindstillinger for lange tastetryk"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Indstillinger for varighed af vibration ved tastetryk"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Indstillinger for lydstyrke ved tastetryk"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Læs ekstern ordbogsfil"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Der er ingen ordbogsfiler i mappen Downloads"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Vælg den ordbog, som du vil installere"</string>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 9816621..3f11379 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android-Tastatur (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android-Tastatureinstellungen (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android-Rechtschreibprüfung (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Einstellungen für die Android-Rechtschreibprüfung (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Eingabeoptionen"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Forschungsprotokollbefehle"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontaktnamen prüfen"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Zum Speichern erneut berühren"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Wörterbuch verfügbar"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Nutzer-Feedback aktivieren"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Tragen Sie zur Verbesserung dieses Eingabemethodeneditors bei, indem Sie automatisch Nutzungsstatistiken und Absturzberichte an Google senden."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tastaturdesign"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Englisch (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Englisch (USA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Später"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Der gleiche Eingabestil ist bereits vorhanden: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Studie zur Benutzerfreundlichkeit"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Verzögerungseinstellungen für langen Tastendruck"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Vibrationsdauer bei Tastendruck"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Tonlautstärke bei Tastendruck"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Externe Wörterbuchdatei lesen"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Keine Wörterbuchdateien im Ordner \"Downloads\""</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Wörterbuchdatei zum Installieren auswählen"</string>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index f3b84c6..fad1cf5 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Πληκτρολόγιο Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Ρυθμίσεις πληκτρολογίου Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Ορθογραφικός έλεγχος Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Ρυθμίσεις ορθογραφικού ελέγχου Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Επιλογές εισόδου"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Έρευνα εντολών καταγραφής"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Αναζήτηση ονομάτων επαφών"</string>
@@ -139,7 +135,7 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Αγγίξτε ξανά για αποθήκευση"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Λεξικό διαθέσιμο"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Ενεργοποίηση σχολίων χρηστών"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Βοηθήστε μας να βελτιώσουμε αυτό το πρόγραμμα επεξεργασίας μεθόδου εισόδου στέλνοντας αυτόματα στατιστικά στοιχεία και αναφορές σφαλμάτων στην Google."</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Βοηθήστε μας να βελτιώσουμε αυτό το πρόγραμμα επεξεργασίας μεθόδου εισόδου, στέλνοντας αυτόματα στατιστικά στοιχεία και αναφορές σφαλμάτων."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Θέμα πληκτρολογίου"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Αγγλικά (Η.Β.)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Αγγλικά (Η.Π.Α)"</string>
@@ -166,9 +162,9 @@
     <string name="not_now" msgid="6172462888202790482">"Όχι τώρα"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Το ίδιο στυλ εισόδου υπάρχει ήδη: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Λειτουργία μελέτης χρηστικότητας"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Ρυθμίσεις καθυστέρησης παρατεταμένου πατήματος πλήκτρου"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Ρυθμίσεις διάρκειας δόνησης κατά το πάτημα πλήκτρων"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Ρυθμίσεις έντασης ήχου κατά το πάτημα πλήκτρων"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Καθυστέρηση παρατεταμένου πατήματος πλήκτρου"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Διάρκεια δόνησης πατήμ. πλήκτ."</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Ένταση ήχου πατήματος πλήκτρου"</string>
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Ανάγνωση εξωτερικού αρχείου λεξικού"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Δεν υπάρχουν αρχεία λεξικού στο φάκελο \"Λήψεις\""</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Επιλογή αρχείου λεξικού για εγκατάσταση"</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index 2dfa8aa..c0b9ede 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android Keyboard (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android Keyboard Settings (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android Spell Checker (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android Spell Checker Settings (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Input options"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Research Log Commands"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Look up contact names"</string>
@@ -139,7 +135,7 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Touch again to save"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dictionary available"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Enable user feedback"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Help improve this input method editor by sending usage statistics and crash reports automatically to Google."</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Help improve this input method editor by automatically sending usage statistics and crash reports"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Keyboard theme"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"English (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"English (US)"</string>
@@ -166,9 +162,9 @@
     <string name="not_now" msgid="6172462888202790482">"Not now"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"The same input style already exists: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Usability study mode"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Key long press delay settings"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Key-press vibration duration settings"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Key-press sound volume settings"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Key long press delay"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Keypress vibration duration"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Keypress sound volume"</string>
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Read external dictionary file"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"No dictionary files in the Downloads folder"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Select a dictionary file to install"</string>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index c7f20aa..6fe85f1 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Teclado de Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Configuración del teclado de Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Corrector ortográfico de Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Configuración del corrector ortográfico de Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opciones de entrada"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Comandos registro invest."</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nombres contactos"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Vuelve a tocar para guardar."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Diccionario disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activar los comentarios del usuario"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Ayuda a mejorar este editor de método de introducción de texto al enviar las estadísticas de uso y los informes de error a Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema del teclado"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglés (Reino Unido)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglés (EE.UU.)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ahora no"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Ya existe el estilo de entrada <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>."</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modo de estudio de usabilidad"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Configuración de retraso de presión prolongada"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Duración de vibración al presionar teclas"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Volumen de sonido al presionar teclas"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Leer archivo de diccionario externo"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"No hay archivos de diccionario en la carpeta de descargas."</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Seleccionar archivo de diccionario para instalar"</string>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index c38ef63..f02cb93 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Teclado Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Ajustes del teclado de Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Corrector de Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Ajustes del corrector de Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opciones entrada texto"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Comandos registro investigación"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Nombres de contactos"</string>
@@ -134,12 +130,13 @@
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entrada de voz inhabilitada"</string>
     <string name="configure_input_method" msgid="373356270290742459">"Configurar métodos de entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas"</string>
-    <string name="send_feedback" msgid="1780431884109392046">"Enviar comentarios"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Danos tu opinión"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de entrada"</string>
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toca otra vez para guardar."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Hay un diccionario disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Habilitar comentarios de usuarios"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Ayuda a mejorar este editor de método de entrada de texto enviando estadísticas de uso e informes de error a Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema de teclado"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"inglés (Reino Unido)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"inglés (EE.UU.)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ahora no"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Ya existe el estilo de entrada <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>."</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modo estudio de usabilidad"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Ajustes de retraso de pulsación prolongada"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Duración de la vibración al pulsar tecla"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Volumen sonido al pulsar tecla"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Leer archivo de diccionario externo"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"No hay archivos de diccionario en la carpeta de descargas."</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Selecciona un archivo de diccionario para instalar"</string>
diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml
index 8968bf4..9d5db66 100644
--- a/java/res/values-et/strings.xml
+++ b/java/res/values-et/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Androidi klaviatuur (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Androidi klaviatuuri seaded (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Androidi õigekirjakontroll (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Androidi õigekirjakontrolli seaded (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Sisestusvalikud"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Uuringulogi käsud"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontakti nimede kontroll."</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Salvestamiseks puudutage uuesti"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Sõnastik saadaval"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Luba kasutaja tagasiside"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Saatke Google\'ile automaatselt kasutusstatistikat ja krahhiaruandeid ning aidake seda sisestusmeetodi redigeerijat parandada."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Klaviatuuri teema"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglise (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglise (USA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Mitte kohe"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Sama sisendstiil on juba olemas: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Kasutatavuse uurimisrežiim"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Pika klahvivajutuse viivituse seaded"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Klahvivajutuse vibratsiooni kestuse seaded"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Klahvivajutuse helitugevuse seaded"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Välise sõnastikufaili lugemine"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Kaustas Allalaadimised pole ühtegi sõnastikufaili"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Installitava sõnastikufaili valimine"</string>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index 723ecd6..3171e17 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"صفحه کلید Android ‏(AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"تنظیمات صفحه کلید Android ‏(AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"غلط‌گیر Android ‏(AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"تنظیمات غلط‌گیر Android ‏(AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"گزینه‌های ورودی"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"فرمان‌های گزارش‌گیری پژوهش"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"جستجوی نام مخاطبین"</string>
@@ -143,7 +139,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"برای ذخیره دوباره لمس کنید"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"دیکشنری موجود است"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"فعال کردن بازخورد کاربر"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"با ارسال خودکار آمارهای کاربرد و گزارش‌های خرابی به Google، به بهبود این ویرایشگر روش ورودی کمک کنید."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"طرح زمینه صفحه‌کلید"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"انگلیسی (بریتانیا)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"انگلیسی (امریکا)"</string>
@@ -170,9 +167,12 @@
     <string name="not_now" msgid="6172462888202790482">"اکنون خیر"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"سبک ورودی مشابهی در حال حاضر وجود دارد: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"حالت بررسی قابلیت استفاده"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"تنظیمات تأخیر فشار طولانی کلید"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"تنظیمات مدت زمان لرزش فشار کلید"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"تنظیمات میزان صدای فشار کلید"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"خواندن فایل فرهنگ لغت خارجی"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"فایل فرهنگ لغتی در پوشه دانلودها وجود ندارد"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"یک فایل فرهنگ لغت برای نصب انتخاب کنید"</string>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 2e6e4d1..c537d32 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android-näppäimistö (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android-näppäimistön asetukset (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android-oikoluku (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android-oikoluvun asetukset (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Syöttövalinnat"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Tutkimuslokin komennot"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Hae kontaktien nimiä"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Tallenna koskettamalla uudelleen"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Sanakirja saatavilla"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Ota käyttäjäpalaute käyttöön"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Auta parantamaan tätä syöttötavan muokkausohjelmaa lähettämällä automaattisesti käyttötietoja ja kaatumisraportteja Googlelle."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Näppäimistöteema"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"englanti (Iso-Britannia)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"englanti (Yhdysvallat)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ei nyt"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Sama tulotyyli on jo olemassa: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Käytettävyystutkimustila"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Näppäimen pitkän painalluksen viiveasetukset"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Näppäimenpainalluksen värinän kestoasetukset"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Näppäimenpainalluksen äänenvoimakkuusasetukset"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Lue ulkoista sanakirjatiedostoa"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Lataukset-kansiossa ei ole sanakirjatiedostoja"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Valitse asennettava sanakirjatiedosto"</string>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 2b8e3cb..c5771a1 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Clavier Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Paramètres du clavier Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Correcteur orthographique Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Paramètres du correcteur orthographique Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Options de saisie"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Commandes journaux rech."</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Rechercher noms contacts"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Appuyer de nouveau pour enregistrer"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dictionnaire disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Autoriser les commentaires des utilisateurs"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Contribuer à l\'amélioration de cet éditeur du mode de saisie grâce à l\'envoi automatique de statistiques d\'utilisation et de rapports d\'incident à Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Thème du clavier"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglais (Royaume-Uni)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglais (États-Unis)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Pas maintenant"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Le style de saisie suivant existe déjà : <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>."</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mode d\'étude de l\'utilisabilité"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Paramètres de temporisation lors d\'un appui prolongé sur une touche"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Durée de vibration à chaque pression"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Volume sonore à chaque pression"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Lire un fichier de dictionnaire externe"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Aucun fichier de dictionnaire dans le dossier \"Téléchargements\""</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Sélectionner un fichier de dictionnaire à installer"</string>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 28c4879..d7f9dc7 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android कीबोर्ड (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android कीबोर्ड सेटिंग (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android वर्तनी परीक्षक (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android वर्तनी परीक्षक सेटिंग (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"इनपुट विकल्‍प"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"लॉग आदेशों का शोध करें"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"संपर्क नामों को खोजें"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"सहेजने के लिए पुन: स्‍पर्श करें"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"शब्‍दकोश उपलब्‍ध है"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"उपयोगकर्ता फ़ीडबैक सक्षम करें"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"उपयोग के आंकड़े और क्रैश रिपोर्ट Google को अपने आप भेज कर इस इनपुट पद्धति संपादक को बेहतर बनाने में सहायता करें."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"कीबोर्ड थीम"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"अंग्रेज़ी (यूके)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"अंग्रेज़ी (यूएस)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"अभी नहीं"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"ऐसी ही इनपुट शैली पहले से मौजूद है: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"उपयोगिता अध्ययन मोड"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"कुंजी को देर तक दबाने के विलंब की सेटिंग"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"कुंजी-स्‍पर्श कंपन अवधि सेटिंग"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"कुंजी-स्‍पर्श ध्‍वनि वॉल्‍यूम सेटिंग"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"बाहरी डिक्शनरी फ़ाइल पढ़ें"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"डाउनलोड फ़ोल्डर में कोई शब्दकोश फ़ाइल नहीं है"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"इंस्टॉल करने के लिए कोई शब्दकोश फ़ाइल चुनें"</string>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index 4732876..6a7139e 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Androidova tipkovnica (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Postavke Androidove tipkovnice (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Androidova provjera pravopisa (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Postavke Androidove provjere pravopisa (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcije ulaza"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Istraživanje naredbi dnevnika"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Potražite imena kontakata"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Dodirnite ponovo za spremanje"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Rječnik je dostupan"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Omogući korisničke povratne informacije"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Pomozite u poboljšanju ovog urednika ulazne metode automatskim slanjem statistike upotrebe i padova Googleu."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema tipkovnice"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engleski (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engleski (SAD)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ne sada"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Već postoji isti stil unosa: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Način studije upotrebljivosti"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Postavke odgode dugog pritiska na tipke"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Postavke trajanja vibracije kod pritiska tipke"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Postavke glasnoće zvuka kod pritiska tipke"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Čitanje datoteke vanjskog rječnika"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"U mapi Preuzimanja nema datoteka rječnika"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Odabir datoteke rječnika za instaliranje"</string>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 68ca03e..1dd9a03 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android-billentyűzet (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android-billentyűzet beállításai (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Androidos helyesírás-ellenőrző (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Androidos helyesírás-ellenőrző beállításai (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Beviteli beállítások"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Naplózási parancsok"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Névjegyek keresése"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Érintse meg újból a mentéshez"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Van elérhető szótár"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Felhasználói visszajelzés engedélyezése"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Segíthet ennek a beviteli módszernek a javításában, ha engedélyezi a használati statisztikák és a hibajelentések elküldését a Google-nak."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Billentyűzettéma"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"angol (brit)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Most nem"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Ugyanez a bemenetstílus már létezik: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Használhatósági teszt"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Gomb hosszú megnyomásának késleltetési beállítása"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Gombnyomás rezgési időtartamának beállításai"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Gombnyomás hangerejének beállításai"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Külső szótárfájl olvasása"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Nincs szótárfájl a Letöltések mappában."</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Válasszon ki egy szótárfájlt a telepítéshez."</string>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index e361e69..cd9b817 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Keyboard Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Setelan Keyboard Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Pemeriksa Ejaan Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Setelan Pemeriksa Ejaan Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opsi masukan"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Riset Perintah Log"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kontak"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Sentuh lagi untuk menyimpan"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Kamus yang tersedia"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktifkan masukan pengguna"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Bantu tingkatkan metode editor masukan dengan mengirim statistik penggunaan dan laporan kerusakan ke Google secara otomatis."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema keyboard"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inggris (Inggris)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inggris (AS)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Nanti saja"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Sudah ada gaya masukan yang sama: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mode studi daya guna"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Setelan penundaan tekan lama tombol"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Setelan durasi getaran saat tombol ditekan"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Setelan volume suara saat tombol ditekan"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Membaca file kamus eksternal"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Tidak ada file kamus di folder Unduhan"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Pilih file kamus untuk dipasang"</string>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index 52c2bfa..c64543a 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Tastiera Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Impostazioni tastiera Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Controllo ortografico Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Impostazioni controllo ortografico Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opzioni inserimento"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Ricerca comandi di log"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cerca in nomi contatti"</string>
@@ -139,7 +135,7 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Tocca di nuovo per salvare"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dizionario disponibile"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Attiva commenti degli utenti"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Aiuta a migliorare l\'editor del metodo di inserimento inviando automaticamente a Google statistiche sull\'utilizzo e segnalazioni sugli arresti anomali."</string>
+    <string name="prefs_description_log" msgid="7525225584555429211">"Contribuisci a migliorare l\'editor del metodo di immissione inviando automaticamente statistiche sull\'utilizzo e rapporti sugli arresti anomali"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema della tastiera"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglese (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglese (USA)"</string>
@@ -166,9 +162,9 @@
     <string name="not_now" msgid="6172462888202790482">"Non ora"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Esiste già uno stile di inuput uguale: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modalità Studio sull\'usabilità"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Impostazioni di ritardo per pressione lunga sui tasti"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Durata vibrazione alla pressione tasto"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Volume audio alla pressione di un tasto"</string>
+    <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"Ritardo pressione lunga tasti"</string>
+    <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"Durata vibraz. pressione tasto"</string>
+    <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"Volume audio a pressione tasto"</string>
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Leggi file dizionario esterno"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Nessun file di dizionario nella cartella Download"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Seleziona un file di dizionario da installare"</string>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index a235937..fa6ccd5 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"מקלדת Android‏ (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"הגדרות מקלדת Android‏ (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"בודק האיות של Android‏ (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"הגדרות בודק האיות של Android‏ (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"אפשרויות קלט"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"פקודות יומן מחקר"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"חפש שמות של אנשי קשר"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"גע שוב כדי לשמור"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"מילון זמין"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"הפוך משוב ממשתמשים לפעיל"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"עזור לשפר את עורך שיטת הקלט על ידי שליחה אוטומטית של סטטיסטיקת שימוש ודוחות קריסת מחשב ל-Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"עיצוב מקלדת"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"אנגלית (בריטניה)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"אנגלית (ארה\"ב)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"לא עכשיו"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"סגנון קלט זהה כבר קיים: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"מצב מחקר שימושיות"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"הגדרות השהייה בעת לחיצה ארוכה על מקש"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"הגדרות משך רטט בלחיצה על מקש"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"הגדרות עוצמת קול בלחיצה על מקש"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"קריאה של קובץ מילון חיצוני"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"אין קובצי מילונים בתיקיית ההורדות"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"בחירת קובץ מילון להתקנה"</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index a3da752..3c618ba 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Androidキーボード(AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Androidキーボードの設定(AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Androidスペルチェッカー(AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Androidスペルチェッカーの設定(AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"入力オプション"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"ログコマンドの検索"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"連絡先名の検索"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"保存するにはもう一度タップ"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"辞書を利用できます"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"ユーザーフィードバックを有効にする"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"IMEの機能向上のため、使用統計状況やクラッシュレポートをGoogleに自動送信します。"</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"キーボードのテーマ"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"英語 (英国)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"英語 (米国)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"後で行う"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"同じ入力スタイルが既に存在します: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"使いやすさの研究モード"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"キーの長押し時間の設定"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"キー操作バイブの振動時間の設定"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"キー操作音の音量設定"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"外部辞書ファイルの読み取り"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"ダウンロードフォルダに辞書ファイルはありません"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"インストールする辞書ファイルの選択"</string>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index 1f4596b..14cb49a 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android 키보드(AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android 키보드 설정(AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android 맞춤법 검사기(AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android 맞춤법 검사기 설정(AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"입력 옵션"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"로그 명령 탐색"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"연락처 이름 조회"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"저장하려면 다시 터치"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"사전 사용 가능"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"사용자 의견 사용"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"사용 통계 및 충돌 보고서를 Google에 자동으로 전송하여 입력 방법 편집기의 개선에 도움을 줍니다."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"키보드 테마"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"영어(영국)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"영어(미국)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"나중에"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"같은 입력 스타일이 다음과 같이 이미 존재합니다. <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"가용성 연구 모드"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"키 길게 누르기 지연 설정"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"키를 누를 때 진동 시간 설정"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"키를 누를 때 효과음 설정"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"외부 사전 파일 읽기"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"다운로드 폴더에 사전 파일이 없음"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"설치할 사전 파일 선택"</string>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index 307b5f2..7b51b21 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"„Android“ klaviatūra (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"„Android“ klaviatūros nustatymai (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"„Android“ rašybos tikrinimo programa (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"„Android“ rašybos tikrinimo programos nustatymai (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Įvesties parinktys"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Tyrinėti žurnalo komandas"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kontaktų vardų paieška"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Jei norite išsaugoti, palieskite dar kartą"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Žodynas galimas"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Įgalinti naudotojų atsiliepimus"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Padėkite patobulinti šią įvesties metodo redagavimo programą automatiškai „Google“ siųsdami naudojimo statistiką ir strigčių ataskaitas."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Klaviatūros tema"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglų k. (JK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglų k. (JAV)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ne dabar"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Toks pat įvesties stilius jau yra: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Tinkamumo tyrimo režimas"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Klavišo ilgo paspaudimo delsos nustatymai"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Vibracijos paspaudus mygtuką trukmės nustatymai"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Garso paspaudus mygtuką garsumo nustatymai"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Skaityti išorinį žodyno failą"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Atsisiuntimų aplanke nėra žodyno failų"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Pasirinkite diegiamą žodyno failą"</string>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index 0241583..676b7c4 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android tastatūra (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android tastatūras iestatījumi (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android pareizrakstības pārbaudītājs (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android pareizrakstības pārbaudītāja iestatījumi (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Ievades opcijas"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Izpētes žurnāla komandas"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Meklēt kontaktp. vārdus"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Pieskarieties vēlreiz, lai saglabātu."</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ir pieejama vārdnīca."</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Iespējot lietotāju atsauksmes"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Palīdziet uzlabot šo ievades metodes redaktoru, automātiski nosūtot lietojuma statistiku un pārskatus par avārijām uzņēmumam Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tastatūras motīvs"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Angļu valoda (Lielbritānija)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Angļu valoda (ASV)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Vēlāk"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Šāds ievades stils jau pastāv: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Lietojamības izpētes režīms"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Taustiņa ilgās nospiešanas noildzes iestatījumi"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Taustiņu nospiešanas vibrācijas ilguma iestatījumi"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Taustiņu nospiešanas skaņas skaļuma iestatījumi"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Ārējās vārdnīcas faila nolasīšana"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Mapē Lejupielādes nav neviena vārdnīcas faila."</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Instalējamā vārdnīcas faila atlasīšana"</string>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index afb036a..96e2b8c 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Papan kekunci Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Tetapan Papan Kekunci Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Penyemak Ejaan Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Tetapan Penyemak Ejaan Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Pilihan input"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Arahan Log Penyelidikan"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Cari nama kenalan"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Sentuh lagi untuk menyimpan"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Kamus tersedia"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Dayakan maklum balas pengguna"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Bantu memperbaik editor input ini dengan menghantar statistik penggunaan dan laporan runtuhan kepada Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema papan kekunci"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Bahasa Inggeris (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Bahasa Inggeris (AS)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Bukan sekarang"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Gaya input yang sama sudah wujud: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Mod kajian kebolehgunaan"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Tetapan kelengahan tekan lama kekunci"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Tetapan tempoh getaran tekan kekunci"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Tetapan kelantangan bunyi tekanan kekunci"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Baca fail kamus luaran"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Tiada fail kamus dalam folder Muat Turun"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Pilih fail kamus untuk dipasang"</string>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index 5db141c..2cac5ba 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android-tastatur (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Instillinger for Android-tastatur (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android-stavekontroll (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Innstillinger for Android-stavekontroll (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inndataalternativer"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Kommandoer for undersøkelseslogging"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Slå opp kontaktnavn"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Trykk på nytt for å lagre"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Ordbok tilgjengelig"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktiver brukertilbakemelding"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Ved å sende bruksstatistikk og programstopprapporter til Google automatisk, hjelper du oss med å gjøre redigeringsfunksjonen for denne inndatametoden enda bedre."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tastaturtema"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engelsk (Storbritannia)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engelsk (USA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ikke nå"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Inndatastilen finnes allerede: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Bruksstudiemodus"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Innstillinger for lange tastetrykk"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Innstillinger for vibrasjonsvarighet ved tastetrykk"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Innstillinger for lydstyrke ved tastetrykk"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Bruk en ekstern ordlistefil"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Det ligger ingen ordboksfiler i Nedlastinger-mappen"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Velg ordboksfilen du vil installere"</string>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index 176f828..93be4ea 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android-toetsenbord (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Instellingen voor het Android-toetsenbord (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Spellingcontrole van Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Instellingen voor spellingcontrole van Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropties"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Opdrachten in onderzoekslogbestand"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Contactnamen opzoeken"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Raak nogmaals aan om op te slaan"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Woordenboek beschikbaar"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Gebruikersfeedback inschakelen."</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Help deze invoermethode te verbeteren door automatisch gebruiksstatistieken en crashmeldingen naar Google te verzenden."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Toetsenbordthema"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engels (GB)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engels (VS)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Niet nu"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Dezelfde invoerstijl bestaat al: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modus voor gebruiksvriendelijkheidsonderzoek"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Instellingen voor vertraging bij toets ingedrukt houden"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Instellingen voor trillingsduur bij druk op een toets"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Instellingen voor geluidsvolume bij druk op een toets"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Extern woordenboekbestand lezen"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Geen woordenboekbestanden in de map \'Downloads\'"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Selecteer een woordenboekbestand om te installeren"</string>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 34a03f6..c9f98f7 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Klawiatura Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Ustawienia klawiatury Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Sprawdzanie pisowni na Androidzie (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Ustawienia sprawdzania pisowni na Androidzie (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcje wprowadzania"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Polecenia dziennika badań"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Przeszukaj kontakty"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Dotknij ponownie, aby zapisać"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Słownik dostępny"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Włącz przesyłanie opinii użytkownika"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Pomóż ulepszyć edytor wprowadzania tekstu, automatycznie wysyłając do Google statystyki użycia i raporty o awariach."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Motyw klawiatury"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Angielska (Wielka Brytania)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Angielska (Stany Zjednoczone)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Nie teraz"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Taki styl wprowadzania już istnieje: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Tryb badania przydatności"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Ustawienia opóźnienia przy przytrzymaniu przycisku"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Czas trwania wibracji przy naciśnięciu"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Głośność dźwięku przy naciśnięciu"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Odczyt zewnętrznego pliku słownika"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Brak plików słownika w folderze Pobrane pliki"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Wybierz plik słownika do zainstalowania"</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 81e1485..ee4c1cb 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Teclado Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Definições do Teclado Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Verificador Ortográfico Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Definições do Verificador Ortográfico Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opções de introdução"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Comandos de Reg. Invest."</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Procurar nomes de contac."</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toque novamente para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activar comentários do utilizador"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Envie automaticamente estatísticas de utilização e relatórios de falhas para a Google e ajude-nos a melhorar este editor de método de introdução."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema do teclado"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglês (RU)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglês (EUA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Agora não"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Já existe o mesmo estilo de introdução: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modo de estudo da capacidade de utilização"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Definições do atraso de pressão longa de tecla"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Definições de duração da vibração ao premir as teclas"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Definições de volume de som ao premir as teclas"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Ler ficheiro de dicionário externo"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Não há ficheiros de dicionário na pasta Transferências"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Selecione um ficheiro de dicionário para instalar"</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 880aca6..1abedd6 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Teclado Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Configurações de teclado Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Corretor ortográfico do Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Configurações de corretor ortográfico do Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opções de entrada"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Pesq. comandos de reg."</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Buscar nomes de contatos"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toque novamente para salvar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicionário disponível"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Ativar comentário do usuário"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Ajude a melhorar este editor de método de entrada enviando automaticamente ao Google estatísticas de uso e relatórios de falhas."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema do teclado"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Inglês (Reino Unido)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inglês (EUA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Agora não"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"O estilo de entrada já existe: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modo de estudo de utilização"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Configurações de atraso ao pressionar teclas"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Configurações de duração da vibração ao tocar a tecla"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Config. volume ao tocar a tecla"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Ler arquivo de dicionário externo"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Nenhum arquivo de dicionário na pasta Downloads"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Selecione um arquivo de dicionário para instalar"</string>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 4733814..15cd327 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -238,7 +238,8 @@
     <skip />
     <string name="has_dictionary" msgid="6071847973466625007">"Dicziunari disponibel"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activar il feedback da l\'utilisader"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Gidai a meglierar quest editur da la metoda d\'endataziun cun trametter automaticamain datas statisticas davart l\'utilisaziun e rapports da collaps a Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <!-- no translation found for keyboard_layout (8451164783510487501) -->
     <skip />
     <!-- no translation found for subtype_en_GB (88170601942311355) -->
@@ -291,11 +292,11 @@
     <skip />
     <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
     <skip />
-    <!-- no translation found for prefs_key_longpress_timeout_settings (1881822418815012326) -->
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
     <skip />
-    <!-- no translation found for prefs_keypress_vibration_duration_settings (1829950405285211668) -->
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
     <skip />
-    <!-- no translation found for prefs_keypress_sound_volume_settings (5875933757082305040) -->
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
     <skip />
     <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
     <skip />
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index a62bfd2..1e052a1 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Tastatură Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Setări tastatură Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Verificator ortografic Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Setări verificator ortografic Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opţiuni de introducere text"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Comenzi jurnal cercetare"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Verificare nume în agendă"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Atingeţi din nou pentru a salva"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Dicţionar disponibil"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Activaţi feedback de la utilizatori"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Ajutaţi la îmbunătăţirea acestui instrument de editare a metodelor de introducere a textului trimiţând în mod automat la Google statistici de utilizare şi rapoarte de blocare."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Temă pentru tastatură"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engleză (Marea Britanie)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engleză (S.U.A.)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Nu acum"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Acelaşi stil de introducere există deja: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modul Studiu privind utilizarea"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Setări pentru întârzierea la apăsarea lungă a tastei"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Setări pentru durata vibrării la apăsarea tastei"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Setări pentru volumul sunetului la apăsarea tastei"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Citiți fișierul de dicționar extern"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Nu există fișiere dicționar în dosarul Descărcări"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Selectați un fișier dicționar de instalat"</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index dab7387..88209b0 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Клавиатура Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Настройки клавиатуры Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Проверка правописания Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Настройки проверки правописания Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Настройки"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Все команды"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Поиск контактов"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Нажмите, чтобы сохранить"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Доступен словарь"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Включить отправку сведений"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Помогите усовершенствовать редактор способа ввода, разрешив отправку статистики и отчетов о сбоях в Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема клавиатуры"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"английский (Великобритания)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"английский (США)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Не сейчас"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Такой стиль ввода уже существует: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим проверки удобства использования"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Долгое нажатие"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Настройки вибросигнала при нажатии клавиш"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Настройки громкости звука при нажатии клавиш"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Считывать данные из внешнего словаря"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"В папке \"Загрузки\" нет словарей"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Выберите файл словаря"</string>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index 33e5485..7fbc1e5 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Klávesnica Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Nastavenia klávesnice Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Kontrola pravopisu (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Nastavenia kontroly pravopisu Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávania textu a údajov"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Príkazy denníka výskumu"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Vyhľadať kontakty"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Opätovným dotykom uložíte"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"K dispozícii je slovník"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Povoliť spätnú väzbu od používateľov"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Automatickým zasielaním štatistík o využívaní editora metódy vstupu a správ o jeho zlyhaní do služby Google môžete prispieť k vylepšeniu tohto nástroja."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Motív klávesnice"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Anglická klávesnica (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglická klávesnica (US)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Teraz nie"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Rovnaký štýl vstupu už existuje: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Režim štúdie použiteľnosti"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Nastavenia oneskorenia pre stlačenie a podržanie klávesu"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Nastavenia trvania vibrovania pri stlačení klávesu"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Nastavenia hlasitosti zvuku pri stlačení klávesu"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Čítať súbor externého slovníka"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"V priečinku Preberanie nie sú žiadne súbory slovníka"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Vyberte súbor slovníka, ktorý chcete nainštalovať"</string>
@@ -191,7 +191,7 @@
     <string name="dictionary_installed" msgid="8081558343559342962">"Nainštalované"</string>
     <string name="dictionary_disabled" msgid="8950383219564621762">"Nainštalovaný, zakázaný"</string>
     <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Probl. s prip. k sl."</string>
-    <string name="no_dictionaries_available" msgid="8039920716566132611">"Slovníky nedostupné"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Slovníky sú nedostupné"</string>
     <string name="check_for_updates_now" msgid="8087688440916388581">"Obnoviť"</string>
     <string name="last_update" msgid="730467549913588780">"Posledná aktualizácia"</string>
     <string name="message_updating" msgid="4457761393932375219">"Prebieha kontrola aktualizácií"</string>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 37540c3..09b0dd5 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Tipkovnica za Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Nastavitve tipkovnice za Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Črkovalnik za Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Nastavitve črkovalnika za Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti vnosa"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Ukazi za dnevnik raziskav"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Iskanje imen stikov"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Dotaknite se še enkrat, da shranite"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Slovar je na voljo"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Omogoči povratne informacije uporabnikov"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"S samodejnim pošiljanjem statističnih podatkov o uporabi in poročil o zrušitvah Googlu nam lahko pomagate izboljšati urejevalnik načina vnosa."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema tipkovnice"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"angleščina (Združeno kraljestvo)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"angleščina (ZDA)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Ne zdaj"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Isti slog vnosa že obstaja: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Način za preučevanje uporabnosti"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Nastavitve zakasnitve za dolg pritisk tipke"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Nastavitve za trajanje vibriranja ob pritisku tipke"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Nastavitve za glasnost zvoka ob pritisku tipke"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Branje zunanje datoteke slovarja"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"V mapi »Prenosi« ni nobene datoteke slovarja"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Izberite datoteko slovarja, ki jo želite namestiti"</string>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 8b8720f..91c9346 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android тастатура (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Подешавања Android тастатуре (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android провера правописа (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Подешавања Android провере правописа (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опције уноса"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Команде евиденције истраживања"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Потражи имена контаката"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Поново додирните да бисте сачували"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Речник је доступан"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Омогући повратну информацију корисника"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Помозите да се побољша овај уређивач режима уноса тако што ће се аутоматски послати статистика о коришћењу и извештаји о грешкама компанији Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема тастатуре"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"енглески (УК)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"енглески (САД)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Не сада"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Исти стил уноса већ постоји: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим за студију могућности коришћења"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Подешавања одлагања при дугом притиску на тастер"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Подешавања трајања вибрације при притиску на тастере"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Подешавања јачине звука при притиску на тастере"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Читање датотеке спољног речника"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"У директоријуму Преузимања нема датотека речника"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Избор датотеке речника за инсталирање"</string>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 6ceed10..643302a 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Androids tangentbord (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Inställningar för Androids tangentbord (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Stavningskontroll i Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Inställningar för Androids stavningskontroll (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inmatningsalternativ"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Loggkommandon"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Sök namn på kontakter"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Spara genom att trycka igen"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"En ordlista är tillgänglig"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Aktivera synpunkter från användare"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Du kan hjälpa till att förbättra inmatningsmetoden genom att automatiskt skicka användningsstatistik och felrapporter till Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tangentbordstema"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Engelskt (brittiskt)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Engelskt (amerikanskt)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Inte nu"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Samma indatastil finns redan: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Läge för studie av användbarhet"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Inställningar för fördröjning vid långt tryck"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Inställningar för vibrationslängd vid knapptryck"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Volyminställningar för knappljud"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Läs extern ordboksfil"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Inga ordboksfiler i mappen Hämtningar"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Välj en ordboksfil att installera"</string>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 1e70e1c..a461795 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Kibodi ya Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Mipangilio ya Kibodi ya Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Kikagua-tahajia cha Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Mipangilio ya Kikagua-tahajia cha Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Chaguo za uingizaji"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Amri za Kumbukumbu za Utafiti"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Angalia majina ya unaowasiliana nao"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Gusa tena ili kuhifadhi"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Kamusi inapatikana"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Wezesha maoni ya watumiaji"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Saidia kuimarisha mbinu ya uingizaji wa kihariri, kwa kutuma takwimu za matumizi na ripoti za kuvurugika kwa Google kiotomatiki."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Maandhari ya kibodi"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Kiingereza cha (Uingereza)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Kiingereza cha (Marekani)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Sio sasa"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Mfumo sawa wa maingizo tayari upo: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modi ya uchunguzi wa utumizi"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"mipangilio ya kuchelewesha kwa kubonyeza kitufe kwa muda mrefu"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Bonyeza mipangilio ya kipindi cha mtetemo"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Bonyeza mipangilio ya nguvu za sauti"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Soma faili ya kamusi ya nje"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Hakuna faili za kamusi katika folda ya Vilivyopakuliwa"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Chagua faili ya kamusi ya kusakinisha"</string>
@@ -181,7 +181,7 @@
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Mtoaji Kamusi"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Huduma ya Kamusi"</string>
     <string name="download_description" msgid="6014835283119198591">"Maelezo ya kusasisha kamusi"</string>
-    <string name="dictionary_settings_title" msgid="8091417676045693313">"Nyongeza za kamusi"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Kamusi za nyongeza"</string>
     <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Kamusi inapatikana"</string>
     <string name="dictionary_settings_summary" msgid="5305694987799824349">"Mipangilio ya kamusi"</string>
     <string name="user_dictionaries" msgid="3582332055892252845">"Kamusi ya mtumiaji"</string>
@@ -193,7 +193,7 @@
     <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Tatizo wakati wa kuunganisha kwenye huduma ya kamusi"</string>
     <string name="no_dictionaries_available" msgid="8039920716566132611">"Hakuna kamusi inayopatikana"</string>
     <string name="check_for_updates_now" msgid="8087688440916388581">"Onyesha upya"</string>
-    <string name="last_update" msgid="730467549913588780">"Mara ya mwisho kusasishwa"</string>
+    <string name="last_update" msgid="730467549913588780">"Ilibadilishwa mwisho"</string>
     <string name="message_updating" msgid="4457761393932375219">"Inatafuta sasisho..."</string>
     <string name="message_loading" msgid="8689096636874758814">"Inapakia..."</string>
     <string name="main_dict_description" msgid="3072821352793492143">"Kamusi kuu"</string>
@@ -201,10 +201,10 @@
     <string name="install_dict" msgid="180852772562189365">"Sakinisha"</string>
     <string name="cancel_download_dict" msgid="7843340278507019303">"Ghairi"</string>
     <string name="delete_dict" msgid="756853268088330054">"Futa"</string>
-    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Lugha iliyochaguliwa kwenye kifaa chako cha mkononi ina kamusi inayopatikana.&lt;br/&gt; Tunapendekeza&lt;b&gt;upakuaji wa kamusi&lt;/b&gt; <xliff:g id="LANGUAGE">%1$s</xliff:g> ili kuboresha hali yako ya kucharaza.&lt;br/&gt; &lt;br/&gt; Upakuaji unaweza kuchukua dakika moja au mbili kukamilika kwenye 3G. Unaweza kutozwa pesa ikiwa huna mpango wa data &lt;b&gt;usio na kipimo &lt;/b&gt;.&lt;br/&gt;Ikiwa huna uhakika na mpango wa data ulio nao, tunapendekeza utafute muunganisho wa Wi-Fi ili uanze upakuaji kiotomatiki.&lt;br/&gt; &lt;br/&gt; Kidokezo: Unaweza kupakua na kuondoa kamusi kwa kuenda kwenye&lt;b&gt;Ingizo la &amp; Lugha&lt;/b&gt; katika &lt;b&gt;menyu ya Mipangilio&lt;/b&gt; ya kifaa chako cha mkononi."</string>
-    <string name="download_over_metered" msgid="1643065851159409546">"Pakua sasa (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
-    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Pakua kupitia kwenye Wi-Fi"</string>
-    <string name="dict_available_notification_title" msgid="6514288591959117288">"Kamusi inapatikana ya <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Lugha iliyochaguliwa kwenye kifaa chako cha mkononi ina kamusi inayopatikana.&lt;br/&gt; Tunapendekeza&lt;b&gt;upakuaji wa kamusi&lt;/b&gt; <xliff:g id="LANGUAGE">%1$s</xliff:g> ili kuboresha hali yako ya kucharaza.&lt;br/&gt; &lt;br/&gt; Upakuaji unaweza kuchukua dakika moja au mbili kukamilika kwenye 3G. Unaweza kutozwa pesa ikiwa huna mpango wa data &lt;b&gt;usio na kipimo &lt;/b&gt;.&lt;br/&gt;Ikiwa huna uhakika una mpango gani wa data, tunapendekeza utafute muunganisho wa Wi-Fi ili uanze upakuaji moja kwa moja.&lt;br/&gt; &lt;br/&gt; Kidokezo: Unaweza kupakua na kuondoa kamusi kwa kuenda kwenye&lt;b&gt;Ingizo la &amp; Lugha&lt;/b&gt; katika &lt;b&gt;menyu ya Mipangilio&lt;/b&gt; ya kifaa chako cha mkononi."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Pakua sasa (MB<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Pakua kwenye Wi-Fi"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"Kamusi ya <xliff:g id="LANGUAGE">%1$s</xliff:g> inapatikana"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Bonyeza ili kukagua na kupakua"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Inapakua: mapendekezo ya <xliff:g id="LANGUAGE">%1$s</xliff:g> yatakuwa tayari hivi karibuni."</string>
 </resources>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 2858076..5279e30 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"แป้นพิมพ์แอนดรอยด์ (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"การตั้งค่าแป้นพิมพ์แอนดรอยด์ (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"เครื่องตรวจตัวสะกดแอนดรอยด์ (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"การตั้งค่าเครื่องตรวจตัวสะกดแอนดรอยด์ (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"ตัวเลือกการป้อนข้อมูล"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"คำสั่งบันทึกการวิจัย"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"ค้นหารายชื่อติดต่อ"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"แตะอีกครั้งเพื่อบัน​​ทึก"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"มีพจนานุกรมให้ใช้งาน"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"เปิดใช้งานการแสดงความคิดเห็นจากผู้ใช้"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"ช่วยปรับปรุงตัวแก้ไขวิธีการป้อนข้อมูลนี้โดยการส่งสถิติการใช้งานและรายงานการขัดข้องถึง Google โดยอัตโนมัติ"</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"ชุดรูปแบบแป้นพิมพ์"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"อังกฤษ (สหราชอาณาจักร)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"อังกฤษ (อเมริกัน)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"ข้ามไปก่อน"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"รูปแบบการป้อนข้อมูลเดียวกันนี้มีอยู่แล้ว: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"โหมดศึกษาประโยชน์ในการใช้งาน"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"การตั้งค่าความหน่วงของการกดแป้นค้าง"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"การตั้งค่าระยะเวลาการสั่นเมื่อกดแป้นพิมพ์"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"การตั้งค่าระดับเสียงเมื่อกดแป้นพิมพ์"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"อ่านไฟล์พจนานุกรมภายนอก"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"ไม่มีไฟล์พจนานุกรมในโฟลเดอร์ดาวน์โหลด"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"เลือกไฟล์พจนานุกรมที่จะติดตั้ง"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 04127ab..11918d3 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android Keyboard (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Mga Setting ng Android Keyboard (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Spell Checker ng Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Mga Setting ng Spell Checker ng Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Mga pagpipilian sa input"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Cmmnd sa Log ng Pnnliksik"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Maghanap pangalan contact"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Pinduting muli upang i-save"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Available ang diksyunaryo"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Paganahin ang feedback ng user"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Tumulong na pahusayin ang editor ng paraan ng pag-input na ito sa pamamagitan ng awtomatikong pagpapadala ng mga istatistika ng paggamit at mga ulat ng crash sa Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema ng keyboard"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Ingles (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Ingles (Estados Unidos)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Hindi ngayon"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Umiiral na ang parehong estilo ng input: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Study mode ng pagiging kapaki-pakinabang"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Mga setting ng pagkaantala ng matagal na pagpindot sa key"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Mga setting ng tagal ng vibration ng keypress"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Mga setting ng volume ng tunog ng keypress"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Magbasa ng panlabas na file ng diksyunaryo"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Walang mga file ng diksyunaryo sa folder na Mga Download"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Pumili ng file ng diksyunaryo na ii-install"</string>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index f238f20..38185bb 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android klavye (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android Klavye Ayarları (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android Yazım Denetleyici (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android Yazım Denetleyici Ayarları (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Giriş seçenekleri"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Araştırma Günlüğü Komutları"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Kişi adlarını denetle"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Kaydetmek için tekrar dokunun"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Sözlük kullanılabilir"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Kullanıcı geri bildirimini etkinleştir"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Kullanım istatistiklerini ve kilitlenme raporlarını Google\'a otomatik olarak göndererek bu giriş yöntemi düzenleyicisinin iyileştirilmesine yardımcı olun."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Klavye teması"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"İngilizce (BK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"İngilizce (ABD)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Şimdi değil"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Aynı giriş stili zaten var: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Kullanılabilirlik çalışması modu"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Tuşa uzun basma için gecikme ayarları"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Tuşa basma titreşim süresi ayarları"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Tuşa basma ses düzeyi ayarları"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Harici sözlük dosyasını oku"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"İndirilenler klasöründe sözlük dosyası yok"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Yüklemek için bir sözlük dosyası seçin"</string>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 42a0b9f..30a37c2 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Клавіатура Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Налаштування клавіатури Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Перевірка орфографії Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Налаштування перевірки орфографії Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Парам. введення"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Команди журналу дослідж."</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Шукати імена контактів"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Торкніться знову, щоб зберегти"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Словник доступний"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Увімк. відгуки корист."</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Допоможіть покращ. редактор методу введ., автомат. надсилаючи в Google статистику використ. та звіти про збої."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема клавіатури"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Англійська (Великобританія)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Англійська (США)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Не зараз"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Такий стиль введення вже існує: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим вивчення зручності у використанні"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Налаштування довгого натискання"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Налаштування тривалості вібрації під час натискання клавіші"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Налаштування гучності звуку під час натискання клавіші"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Читати файл зовнішнього словника"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"У папці \"Завантаження\" немає файлів словника"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Вибрати файл словника, який потрібно встановити"</string>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index 245e685..5670106 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Bàn phím Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Cài đặt bàn phím Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Trình kiểm tra chính tả Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Cài đặt trình kiểm tra chính tả Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Tùy chọn nhập"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Lệnh ghi nhật ký cho nghiên cứu"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Tra cứu tên liên hệ"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Chạm lại để lưu"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Có sẵn từ điển"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Bật phản hồi của người dùng"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Giúp nâng cao trình chỉnh sửa phương thức nhập này bằng cách tự động gửi thống kê sử dụng và báo cáo sự cố cho Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Chủ đề bàn phím"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Tiếng Anh (Anh)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Tiếng Anh (Mỹ)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Để sau"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Đã tồn tại kiểu nhập tương tự: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Chế độ nghiên cứu tính khả dụng"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Cài đặt thời gian chờ cho nhấn và giữ phím"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Cài đặt thời gian rung khi nhấn phím"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Cài đặt âm lượng khi nhấn phím"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Đọc tệp từ điển bên ngoài"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Không có tệp từ điển nào trong thư mục Nội dung tải xuống"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Chọn tệp từ điển để cài đặt"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index cba3322..3054d13 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android 键盘 (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android 键盘设置 (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android 拼写检查工具 (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android 拼写检查工具设置 (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"输入选项"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"研究记录命令"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查找联系人姓名"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"再次触摸即可保存"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"有可用词典"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"启用用户反馈"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"自动向 Google 发送使用情况统计信息和崩溃报告,帮助改进该输入法编辑器。"</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"键盘主题"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"英语(英国)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"英语(美国)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"以后再说"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"已经存在相同的输入风格:<xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"可用性研究模式"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"按键长按延迟设置"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"按键振动持续时间设置"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"按键音量设置"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"读取外部词典文件"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"“下载内容”文件夹中没有词典文件"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"选择要安装的词典文件"</string>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 9a48969..d9d4751 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Android 鍵盤 (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Android 鍵盤設定 (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Android 拼字檢查 (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Android 拼字檢查設定 (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"研究紀錄指令"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"查詢聯絡人姓名"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"再次輕觸即可儲存"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"可使用字典"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"啟用使用者意見回饋"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"自動將使用統計資料和當機報告傳送給 Google,協助改善這個輸入法編輯器。"</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"鍵盤主題"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"英文 (英式)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"英文 (美式)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"暫時不要"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"已存在相同的輸入樣式:<xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"使用習慣學習模式"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"按鍵長按延遲設定"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"按鍵震動持續時間設定"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"按鍵音量設定"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"讀取外部字典檔案"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"「下載」資料夾中沒有任何字典檔案"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"選取要安裝的字典檔案"</string>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index e360750..93c8d92 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -20,14 +20,10 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (8250992613616792321) -->
-    <skip />
-    <!-- no translation found for aosp_android_keyboard_ime_settings (423615877174850267) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (511950477199948048) -->
-    <skip />
-    <!-- no translation found for aosp_android_spell_checker_service_settings (2970535894327288421) -->
-    <skip />
+    <string name="aosp_android_keyboard_ime_name" msgid="8250992613616792321">"Ikhibhodi ye-Android (AOSP)"</string>
+    <string name="aosp_android_keyboard_ime_settings" msgid="423615877174850267">"Izilungiselelo zekhibhodi ye-Android (AOSP)"</string>
+    <string name="aosp_spell_checker_service_name" msgid="511950477199948048">"Isihloli sokupela se-Android (AOSP)"</string>
+    <string name="aosp_android_spell_checker_service_settings" msgid="2970535894327288421">"Izilungiselelo zesihloli sokupela se-Android (AOSP)"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Okukhethwa kukho kokungenayo"</string>
     <string name="english_ime_research_log" msgid="8492602295696577851">"Imiyalo yefayela lokungena lokucwaninga"</string>
     <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"Bheka amagama woxhumana nabo"</string>
@@ -139,7 +135,8 @@
     <string name="hint_add_to_dictionary" msgid="573678656946085380">"Thinta futhi ukuze ulondoloze"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Isichazamazwi siyatholakala"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Vumela impendulo yomsebenzisi"</string>
-    <string name="prefs_description_log" msgid="5827825607258246003">"Siza ukuthuthukisa lo mhleli wendlela yokufakwa ngokusithumela ngokuzenzakalela izibalo zokusetshenziswa nokukhubeka ku-Google."</string>
+    <!-- no translation found for prefs_description_log (7525225584555429211) -->
+    <skip />
     <string name="keyboard_layout" msgid="8451164783510487501">"Indikimba yekhibhodi"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"i-English(UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"i-English (US)"</string>
@@ -166,9 +163,12 @@
     <string name="not_now" msgid="6172462888202790482">"Hhayi manje"</string>
     <string name="custom_input_style_already_exists" msgid="8008728952215449707">"Isitayela sokufaka esifanayo sesivele sikhona: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Imodi yesitadi yokusebenziseka"</string>
-    <string name="prefs_key_longpress_timeout_settings" msgid="1881822418815012326">"Izilungiselelo zokulibazisa ukucindezelwa ukhiye isikhathi eside"</string>
-    <string name="prefs_keypress_vibration_duration_settings" msgid="1829950405285211668">"Izilungiselelo ze-keypress vibration duraton"</string>
-    <string name="prefs_keypress_sound_volume_settings" msgid="5875933757082305040">"Izilungiselelo zevolumu yomsindo wekeypress"</string>
+    <!-- no translation found for prefs_key_longpress_timeout_settings (6102240298932897873) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_vibration_duration_settings (7918341459947439226) -->
+    <skip />
+    <!-- no translation found for prefs_keypress_sound_volume_settings (6027007337036891623) -->
+    <skip />
     <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"Funda ifayela elangaphandle lesichazamazwi"</string>
     <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"Awekho amafayela wesichazamazwi kufolda yokulandiwe"</string>
     <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"Khetha ifayela lesichazamazwi ukuze ulifake"</string>
diff --git a/java/res/values/dictionary-pack.xml b/java/res/values/dictionary-pack.xml
index 4109bcb..3fdc671 100644
--- a/java/res/values/dictionary-pack.xml
+++ b/java/res/values/dictionary-pack.xml
@@ -20,4 +20,8 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="dictionary_pack_client_id" translatable="false">com.android.inputmethod.latin</string>
     <string name="dictionary_pack_metadata_uri" translatable="false"></string>
+    <string name="dictionary_pack_settings_activity">com.android.inputmethod.dictionarypack.DictionarySettingsActivity</string>
+    <string name="authority">com.android.inputmethod.dictionarypack.aosp</string>
+    <string name="default_metadata_uri"></string>
+    <string name="local_metadata_filename">metadata.json</string>
 </resources>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 1e70fbb..d2554ee 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -211,11 +211,4 @@
     </string-array>
 
     <string name="settings_warning_researcher_mode">Attention!  You are using the special keyboard for research purposes.</string>
-
-    <!-- dictionary pack settings -->
-    <string name="dictionary_pack_settings_activity">com.android.inputmethod.dictionarypack.DictionarySettingsActivity</string>
-    <string name="authority">com.android.inputmethod.dictionarypack.aosp</string>
-    <string name="default_metadata_uri"></string>
-    <string name="local_metadata_filename">metadata.json</string>
-
 </resources>
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index 9b1d543..10400be 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -18,13 +18,17 @@
 */
 -->
 <resources>
+    <!-- Build.HARDWARE,duration_in_milliseconds -->
     <string-array name="keypress_vibration_durations" translatable="false">
-        <!-- Build.HARDWARE,duration_in_milliseconds -->
+        <!-- Nexus S -->
         <item>herring,5</item>
+        <!-- Galaxy Nexus -->
         <item>tuna,5</item>
+        <!-- Nexus 4 -->
         <item>mako,5</item>
+        <!-- Nexus 10 -->
         <item>manta,16</item>
         <!-- Default value for unknown device -->
-        <item>DEFAULT,10</item>
+        <item>DEFAULT,20</item>
     </string-array>
 </resources>
diff --git a/java/res/values/setup-wizard.xml b/java/res/values/setup-wizard.xml
new file mode 100644
index 0000000..8464709
--- /dev/null
+++ b/java/res/values/setup-wizard.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<resources>
+    <bool name="config_setup_wizard_available">false</bool>
+</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index ebcd3d9..201fc70 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -350,10 +350,10 @@
     <!-- Inform the user that a particular language has an available dictionary -->
     <string name="has_dictionary">Dictionary available</string>
 
-    <!-- Preferences item for enabling to send user statistics to Google -->
+    <!-- Preferences item for enabling to send user statistics for development only diagnostics -->
     <string name="prefs_enable_log">Enable user feedback</string>
-    <!-- Description for enabling to send user statistics to Google -->
-    <string name="prefs_description_log">Help improve this input method editor by automatically sending usage statistics and crash reports to Google.</string>
+    <!-- Description for enabling to send user statistics for development only diagnostics -->
+    <string name="prefs_description_log">Help improve this input method editor by automatically sending usage statistics and crash reports</string>
 
     <!-- Title of the item to change the keyboard theme [CHAR LIMIT=20]-->
     <string name="keyboard_layout">Keyboard theme</string>
@@ -422,12 +422,12 @@
 
     <!-- Title of an option for usability study mode -->
     <string name="prefs_usability_study_mode">Usability study mode</string>
-    <!-- Title of the settings for key long press delay -->
-    <string name="prefs_key_longpress_timeout_settings">Key long press delay settings</string>
-    <!-- Title of the settings for keypress vibration duration -->
-    <string name="prefs_keypress_vibration_duration_settings">Keypress vibration duration settings</string>
-    <!-- Title of the settings for keypress sound volume -->
-    <string name="prefs_keypress_sound_volume_settings">Keypress sound volume settings</string>
+    <!-- Title of the settings for key long press delay [CHAR LIMIT=30] -->
+    <string name="prefs_key_longpress_timeout_settings">Key long press delay</string>
+    <!-- Title of the settings for keypress vibration duration [CHAR LIMIT=30] -->
+    <string name="prefs_keypress_vibration_duration_settings">Keypress vibration duration</string>
+    <!-- Title of the settings for keypress sound volume [CHAR LIMIT=30] -->
+    <string name="prefs_keypress_sound_volume_settings">Keypress sound volume</string>
     <!-- Title of the settings for reading an external dictionary file -->
     <string name="prefs_read_external_dictionary">Read external dictionary file</string>
     <!-- Message to show when there are no files to install as an external dictionary [CHAR LIMIT=100] -->
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 7839462..a13021b 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -90,6 +90,7 @@
             android:summary="@string/gesture_input_summary"
             android:persistent="true"
             android:defaultValue="true" />
+        <!-- TODO: Move these two options to the advanced settings. -->
         <CheckBoxPreference
             android:key="pref_gesture_floating_preview_text"
             android:title="@string/gesture_floating_preview_text"
@@ -139,10 +140,6 @@
                 android:summary="@string/include_other_imes_in_language_switch_list_summary"
                 android:persistent="true"
                 android:defaultValue="false" />
-            <PreferenceScreen
-                android:fragment="com.android.inputmethod.latin.AdditionalSubtypeSettings"
-                android:key="custom_input_styles"
-                android:title="@string/custom_input_styles_title" />
             <!-- Values for popup dismiss delay are added programmatically -->
             <CheckBoxPreference
                 android:key="pref_sliding_key_input_preview"
@@ -150,6 +147,10 @@
                 android:summary="@string/sliding_key_input_preview_summary"
                 android:persistent="true"
                 android:defaultValue="true" />
+            <PreferenceScreen
+                android:fragment="com.android.inputmethod.latin.AdditionalSubtypeSettings"
+                android:key="custom_input_styles"
+                android:title="@string/custom_input_styles_title" />
             <ListPreference
                 android:key="pref_key_preview_popup_dismiss_delay"
                 android:title="@string/key_preview_popup_dismiss_delay" />
diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
new file mode 100644
index 0000000..ff6561c
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.Context;
+import android.provider.UserDictionary.Words;
+
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+public final class UserDictionaryCompatUtils {
+    // UserDictionary.Words#addWord(Context, String, int, String, Locale) was introduced
+    // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+    private static final Method METHOD_addWord = CompatUtils.getMethod(Words.class, "addWord",
+            Context.class, String.class, Integer.TYPE, String.class, Locale.class);
+
+    public static void addWord(final Context context, final String word, final int freq,
+            final String shortcut, final Locale locale) {
+        if (hasNewerAddWord()) {
+            CompatUtils.invoke(Words.class, null, METHOD_addWord, context, word, freq, shortcut,
+                    locale);
+        } else {
+            // Fall back to the pre-JellyBean method.
+            final int localeType;
+            if (null == locale) {
+                localeType = Words.LOCALE_TYPE_ALL;
+            } else {
+                localeType = Words.LOCALE_TYPE_CURRENT;
+            }
+            Words.addWord(context, word, freq, localeType);
+        }
+    }
+
+    private static final boolean hasNewerAddWord() {
+        return null != METHOD_addWord;
+    }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
index 0c8b466..6961588 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
@@ -25,16 +25,34 @@
  */
 public class DictionaryPackConstants {
     /**
+     * The root domain for the dictionary pack, upon which authorities and actions will append
+     * their own distinctive strings.
+     */
+    private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack";
+
+    /**
      * Authority for the ContentProvider protocol.
      */
     // TODO: find some way to factorize this string with the one in the resources
-    public static final String AUTHORITY = "com.android.inputmethod.dictionarypack.aosp";
+    public static final String AUTHORITY = DICTIONARY_DOMAIN + ".aosp";
 
     /**
      * The action of the intent for publishing that new dictionary data is available.
      */
     // TODO: make this different across different packages. A suggested course of action is
     // to use the package name inside this string.
-    public static final String NEW_DICTIONARY_INTENT_ACTION =
-            "com.android.inputmethod.dictionarypack.newdict";
+    // NOTE: The appended string should be uppercase like all other actions, but it's not for
+    // historical reasons.
+    public static final String NEW_DICTIONARY_INTENT_ACTION = DICTIONARY_DOMAIN + ".newdict";
+
+    /**
+     * The action of the intent sent by the dictionary pack to ask for a client to make
+     * itself known. This is used when the settings activity is brought up for a client the
+     * dictionary pack does not know about.
+     */
+    public static final String UNKNOWN_DICTIONARY_PROVIDER_CLIENT = DICTIONARY_DOMAIN
+            + ".UNKNOWN_CLIENT";
+    // In the above intents, the name of the string extra that contains the name of the client
+    // we want information about.
+    public static final String DICTIONARY_PROVIDER_CLIENT_EXTRA = "client";
 }
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index 77b3b8e..f8d1c4f 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -509,6 +509,11 @@
                 } catch (final BadFormatException e) {
                     Log.w(TAG, "Not enough information to insert this dictionary " + values, e);
                 }
+                // We just received new information about the list of dictionary for this client.
+                // For all intents and purposes, this is new metadata, so we should publish it
+                // so that any listeners (like the Settings interface for example) can update
+                // themselves.
+                UpdateHandler.publishUpdateMetadataCompleted(getContext(), true);
                 break;
             case DICTIONARY_V1_WHOLE_LIST:
             case DICTIONARY_V1_DICT_INFO:
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 7e2a6bb..9e27c1f 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -110,6 +110,15 @@
         super.onResume();
         mChangedSettings = false;
         UpdateHandler.registerUpdateEventListener(this);
+        final Activity activity = getActivity();
+        if (!MetadataDbHelper.isClientKnown(activity, mClientId)) {
+            Log.i(TAG, "Unknown dictionary pack client: " + mClientId + ". Requesting info.");
+            final Intent unknownClientBroadcast =
+                    new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT);
+            unknownClientBroadcast.putExtra(
+                    DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId);
+            activity.sendBroadcast(unknownClientBroadcast);
+        }
         final IntentFilter filter = new IntentFilter();
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         getActivity().registerReceiver(mConnectivityChangedReceiver, filter);
@@ -130,6 +139,7 @@
         }
     }
 
+    @Override
     public void downloadedMetadata(final boolean succeeded) {
         stopLoadingAnimation();
         if (!succeeded) return; // If the download failed nothing changed, so no need to refresh
@@ -141,6 +151,7 @@
         }.start();
     }
 
+    @Override
     public void wordListDownloadFinished(final String wordListId, final boolean succeeded) {
         final WordListPreference pref = findWordListPreference(wordListId);
         if (null == pref) return;
@@ -177,6 +188,7 @@
         return null;
     }
 
+    @Override
     public void updateCycleCompleted() {}
 
     private void refreshNetworkState() {
@@ -260,6 +272,7 @@
         } else if (!cursor.moveToFirst()) {
             final ArrayList<Preference> result = new ArrayList<Preference>();
             result.add(createErrorMessage(activity, R.string.no_dictionaries_available));
+            cursor.close();
             return result;
         } else {
             final String systemLocaleString = Locale.getDefault().toString();
@@ -289,6 +302,7 @@
                     prefList.put(key, pref);
                 }
             } while (cursor.moveToNext());
+            cursor.close();
             return prefList.values();
         }
     }
@@ -335,8 +349,7 @@
     private void cancelRefresh() {
         UpdateHandler.unregisterUpdateEventListener(this);
         final Context context = getActivity();
-        UpdateHandler.cancelUpdate(context,
-                MetadataDbHelper.getMetadataUriAsString(context, mClientId));
+        UpdateHandler.cancelUpdate(context, mClientId);
         stopLoadingAnimation();
     }
 
@@ -359,7 +372,12 @@
                             getActivity(), android.R.anim.fade_out));
                     preferenceView.startAnimation(AnimationUtils.loadAnimation(
                             getActivity(), android.R.anim.fade_in));
-                    mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
+                    // The menu is created by the framework asynchronously after the activity,
+                    // which means it's possible to have the activity running but the menu not
+                    // created yet - hence the necessity for a null check here.
+                    if (null != mUpdateNowMenu) {
+                        mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
+                    }
                 }
             });
     }
diff --git a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
index 96c4a83..d8aa33b 100644
--- a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
@@ -16,13 +16,9 @@
 
 package com.android.inputmethod.dictionarypack;
 
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.R;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.util.Log;
 
 public final class EventHandler extends BroadcastReceiver {
     private static final String TAG = EventHandler.class.getName();
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index b472750..e05a79b 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -444,7 +444,19 @@
         manager.remove(fileId);
     }
 
-    private static void publishUpdateMetadataCompleted(final Context context,
+    /**
+     * Sends a broadcast informing listeners that the dictionaries were updated.
+     *
+     * This will call all local listeners through the UpdateEventListener#downloadedMetadata
+     * callback (for example, the dictionary provider interface uses this to stop the Loading
+     * animation) and send a broadcast about the metadata having been updated. For a client of
+     * the dictionary pack like Latin IME, this means it should re-query the dictionary pack
+     * for any relevant new data.
+     *
+     * @param context the context, to send the broadcast.
+     * @param downloadSuccessful whether the download of the metadata was successful or not.
+     */
+    public static void publishUpdateMetadataCompleted(final Context context,
             final boolean downloadSuccessful) {
         // We need to warn all listeners of what happened. But some listeners may want to
         // remove themselves or re-register something in response. Hence we should take a
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 43d28be..e4e75c3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -26,10 +26,8 @@
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.SparseArray;
 import android.view.View;
 
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
@@ -73,15 +71,15 @@
  */
 public class KeyboardView extends View {
     // XML attributes
-    protected final KeyVisualAttributes mKeyVisualAttributes;
+    private final KeyVisualAttributes mKeyVisualAttributes;
     private final int mKeyLabelHorizontalPadding;
     private final float mKeyHintLetterPadding;
     private final float mKeyPopupHintLetterPadding;
     private final float mKeyShiftedLetterHintPadding;
     private final float mKeyTextShadowRadius;
-    protected final float mVerticalCorrection;
-    protected final Drawable mKeyBackground;
-    protected final Rect mKeyBackgroundPadding = new Rect();
+    private final float mVerticalCorrection;
+    private final Drawable mKeyBackground;
+    private final Rect mKeyBackgroundPadding = new Rect();
 
     // HORIZONTAL ELLIPSIS "...", character for popup hint.
     private static final String POPUP_HINT_CHAR = "\u2026";
@@ -113,10 +111,6 @@
     private final Canvas mOffscreenCanvas = new Canvas();
     private final Paint mPaint = new Paint();
     private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
-    // This sparse array caches key label text height in pixel indexed by key label text size.
-    private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
-    // This sparse array caches key label text width in pixel indexed by key label text size.
-    private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
     private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
     private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
 
@@ -134,15 +128,15 @@
         mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset(
                 R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
         mKeyHintLetterPadding = keyboardViewAttr.getDimension(
-                R.styleable.KeyboardView_keyHintLetterPadding, 0);
+                R.styleable.KeyboardView_keyHintLetterPadding, 0.0f);
         mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension(
-                R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
+                R.styleable.KeyboardView_keyPopupHintLetterPadding, 0.0f);
         mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
-                R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
+                R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f);
         mKeyTextShadowRadius = keyboardViewAttr.getFloat(
                 R.styleable.KeyboardView_keyTextShadowRadius, 0.0f);
         mVerticalCorrection = keyboardViewAttr.getDimension(
-                R.styleable.KeyboardView_verticalCorrection, 0);
+                R.styleable.KeyboardView_verticalCorrection, 0.0f);
         keyboardViewAttr.recycle();
 
         final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
@@ -185,6 +179,14 @@
         return mKeyboard;
     }
 
+    protected float getVerticalCorrection() {
+        return mVerticalCorrection;
+    }
+
+    protected void updateKeyDrawParams(final int keyHeight) {
+        mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+    }
+
     @Override
     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
         if (mKeyboard != null) {
@@ -213,7 +215,7 @@
             }
             onDrawKeyboard(mOffscreenCanvas);
         }
-        canvas.drawBitmap(mOffscreenBuffer, 0, 0, null);
+        canvas.drawBitmap(mOffscreenBuffer, 0.0f, 0.0f, null);
     }
 
     private boolean maybeAllocateOffscreenBuffer() {
@@ -333,7 +335,7 @@
         canvas.translate(bgX, bgY);
         background.draw(canvas);
         if (LatinImeLogger.sVISUALDEBUG) {
-            drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint());
+            drawRectangle(canvas, 0.0f, 0.0f, bgWidth, bgHeight, 0x80c00000, new Paint());
         }
         canvas.translate(-bgX, -bgY);
     }
@@ -347,7 +349,7 @@
         final float centerY = keyHeight * 0.5f;
 
         if (LatinImeLogger.sVISUALDEBUG) {
-            drawRectangle(canvas, 0, 0, keyWidth, keyHeight, 0x800000c0, new Paint());
+            drawRectangle(canvas, 0.0f, 0.0f, keyWidth, keyHeight, 0x800000c0, new Paint());
         }
 
         // Draw key label.
@@ -357,14 +359,16 @@
             final String label = key.mLabel;
             paint.setTypeface(key.selectTypeface(params));
             paint.setTextSize(key.selectTextSize(params));
-            final float labelCharHeight = getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
-            final float labelCharWidth = getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
+            final float labelCharHeight = TypefaceUtils.getCharHeight(
+                    KEY_LABEL_REFERENCE_CHAR, paint);
+            final float labelCharWidth = TypefaceUtils.getCharWidth(
+                    KEY_LABEL_REFERENCE_CHAR, paint);
 
             // Vertical label text alignment.
-            final float baseline = centerY + labelCharHeight / 2;
+            final float baseline = centerY + labelCharHeight / 2.0f;
 
             // Horizontal label text alignment
-            float labelWidth = 0;
+            float labelWidth = 0.0f;
             if (key.isAlignLeft()) {
                 positionX = mKeyLabelHorizontalPadding;
                 paint.setTextAlign(Align.LEFT);
@@ -373,31 +377,31 @@
                 paint.setTextAlign(Align.RIGHT);
             } else if (key.isAlignLeftOfCenter()) {
                 // TODO: Parameterise this?
-                positionX = centerX - labelCharWidth * 7 / 4;
+                positionX = centerX - labelCharWidth * 7.0f / 4.0f;
                 paint.setTextAlign(Align.LEFT);
             } else if (key.hasLabelWithIconLeft() && icon != null) {
-                labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+                labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth()
                         + LABEL_ICON_MARGIN * keyWidth;
-                positionX = centerX + labelWidth / 2;
+                positionX = centerX + labelWidth / 2.0f;
                 paint.setTextAlign(Align.RIGHT);
             } else if (key.hasLabelWithIconRight() && icon != null) {
-                labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+                labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth()
                         + LABEL_ICON_MARGIN * keyWidth;
-                positionX = centerX - labelWidth / 2;
+                positionX = centerX - labelWidth / 2.0f;
                 paint.setTextAlign(Align.LEFT);
             } else {
                 positionX = centerX;
                 paint.setTextAlign(Align.CENTER);
             }
             if (key.needsXScale()) {
-                paint.setTextScaleX(
-                        Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
+                paint.setTextScaleX(Math.min(1.0f,
+                        (keyWidth * MAX_LABEL_RATIO) / TypefaceUtils.getLabelWidth(label, paint)));
             }
 
             paint.setColor(key.selectTextColor(params));
             if (key.isEnabled()) {
                 // Set a drop shadow for the text
-                paint.setShadowLayer(mKeyTextShadowRadius, 0, 0, params.mTextShadowColor);
+                paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
             } else {
                 // Make label invisible
                 paint.setColor(Color.TRANSPARENT);
@@ -405,7 +409,7 @@
             blendAlpha(paint, params.mAnimAlpha);
             canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
             // Turn off drop shadow and reset x-scale.
-            paint.setShadowLayer(0, 0, 0, 0);
+            paint.setShadowLayer(0.0f, 0.0f, 0.0f, Color.TRANSPARENT);
             paint.setTextScaleX(1.0f);
 
             if (icon != null) {
@@ -413,10 +417,10 @@
                 final int iconHeight = icon.getIntrinsicHeight();
                 final int iconY = (keyHeight - iconHeight) / 2;
                 if (key.hasLabelWithIconLeft()) {
-                    final int iconX = (int)(centerX - labelWidth / 2);
+                    final int iconX = (int)(centerX - labelWidth / 2.0f);
                     drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
                 } else if (key.hasLabelWithIconRight()) {
-                    final int iconX = (int)(centerX + labelWidth / 2 - iconWidth);
+                    final int iconX = (int)(centerX + labelWidth / 2.0f - iconWidth);
                     drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
                 }
             }
@@ -439,20 +443,23 @@
                 // The hint label is placed just right of the key label. Used mainly on
                 // "phone number" layout.
                 // TODO: Generalize the following calculations.
-                hintX = positionX + getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2;
-                hintY = centerY + getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
+                hintX = positionX
+                        + TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2.0f;
+                hintY = centerY
+                        + TypefaceUtils.getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
                 paint.setTextAlign(Align.LEFT);
             } else if (key.hasShiftedLetterHint()) {
                 // The hint label is placed at top-right corner of the key. Used mainly on tablet.
                 hintX = keyWidth - mKeyShiftedLetterHintPadding
-                        - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
+                        - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
                 paint.getFontMetrics(mFontMetrics);
                 hintY = -mFontMetrics.top;
                 paint.setTextAlign(Align.CENTER);
             } else { // key.hasHintLetter()
                 // The hint letter is placed at top-right corner of the key. Used mainly on phone.
                 hintX = keyWidth - mKeyHintLetterPadding
-                        - getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2;
+                        - TypefaceUtils.getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint)
+                        / 2.0f;
                 hintY = -paint.ascent();
                 paint.setTextAlign(Align.CENTER);
             }
@@ -506,7 +513,7 @@
         paint.setColor(params.mHintLabelColor);
         paint.setTextAlign(Align.CENTER);
         final float hintX = keyWidth - mKeyHintLetterPadding
-                - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
+                - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
         final float hintY = keyHeight - mKeyPopupHintLetterPadding;
         canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
 
@@ -517,54 +524,6 @@
         }
     }
 
-    private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
-        final int labelSize = (int)paint.getTextSize();
-        final Typeface face = paint.getTypeface();
-        final int codePointOffset = referenceChar << 15;
-        if (face == Typeface.DEFAULT) {
-            return codePointOffset + labelSize;
-        } else if (face == Typeface.DEFAULT_BOLD) {
-            return codePointOffset + labelSize + 0x1000;
-        } else if (face == Typeface.MONOSPACE) {
-            return codePointOffset + labelSize + 0x2000;
-        } else {
-            return codePointOffset + labelSize;
-        }
-    }
-
-    // Working variable for the following methods.
-    private final Rect mTextBounds = new Rect();
-
-    private float getCharHeight(final char[] referenceChar, final Paint paint) {
-        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
-        final Float cachedValue = sTextHeightCache.get(key);
-        if (cachedValue != null)
-            return cachedValue;
-
-        paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
-        final float height = mTextBounds.height();
-        sTextHeightCache.put(key, height);
-        return height;
-    }
-
-    private float getCharWidth(final char[] referenceChar, final Paint paint) {
-        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
-        final Float cachedValue = sTextWidthCache.get(key);
-        if (cachedValue != null)
-            return cachedValue;
-
-        paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
-        final float width = mTextBounds.width();
-        sTextWidthCache.put(key, width);
-        return width;
-    }
-
-    // TODO: Remove this method.
-    public float getLabelWidth(final String label, final Paint paint) {
-        paint.getTextBounds(label, 0, label.length(), mTextBounds);
-        return mTextBounds.width();
-    }
-
     protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x,
             final int y, final int width, final int height) {
         canvas.translate(x, y);
@@ -578,7 +537,7 @@
         paint.setStyle(Paint.Style.STROKE);
         paint.setStrokeWidth(1.0f);
         paint.setColor(color);
-        canvas.drawLine(0, y, w, y, paint);
+        canvas.drawLine(0.0f, y, w, y, paint);
     }
 
     private static void drawVerticalLine(final Canvas canvas, final float x, final float h,
@@ -586,7 +545,7 @@
         paint.setStyle(Paint.Style.STROKE);
         paint.setStrokeWidth(1.0f);
         paint.setColor(color);
-        canvas.drawLine(x, 0, x, h, paint);
+        canvas.drawLine(x, 0.0f, x, h, paint);
     }
 
     private static void drawRectangle(final Canvas canvas, final float x, final float y,
@@ -595,15 +554,20 @@
         paint.setStrokeWidth(1.0f);
         paint.setColor(color);
         canvas.translate(x, y);
-        canvas.drawRect(0, 0, w, h, paint);
+        canvas.drawRect(0.0f, 0.0f, w, h, paint);
         canvas.translate(-x, -y);
     }
 
-    public Paint newDefaultLabelPaint() {
+    public Paint newLabelPaint(final Key key) {
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
-        paint.setTypeface(mKeyDrawParams.mTypeface);
-        paint.setTextSize(mKeyDrawParams.mLabelSize);
+        if (key == null) {
+            paint.setTypeface(mKeyDrawParams.mTypeface);
+            paint.setTextSize(mKeyDrawParams.mLabelSize);
+        } else {
+            paint.setTypeface(key.selectTypeface(mKeyDrawParams));
+            paint.setTextSize(key.selectTextSize(mKeyDrawParams));
+        }
         return paint;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index f0ca9c1..ba78d01 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -229,6 +229,9 @@
         @Override
         public void handleMessage(final Message msg) {
             final MainKeyboardView keyboardView = getOuterInstance();
+            if (keyboardView == null) {
+                return;
+            }
             final PointerTracker tracker = (PointerTracker) msg.obj;
             switch (msg.what) {
             case MSG_TYPING_STATE_EXPIRED:
@@ -527,9 +530,9 @@
                 R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
 
         final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
-                R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
+                R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
         final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
-                R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0);
+                R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
         mKeyDetector = new KeyDetector(
                 keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
         mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr);
@@ -652,7 +655,7 @@
         mKeyTimerHandler.cancelLongPressTimer();
         super.setKeyboard(keyboard);
         mKeyDetector.setKeyboard(
-                keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
+                keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
         PointerTracker.setKeyDetector(mKeyDetector);
         mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth);
         mMoreKeysKeyboardCache.clear();
@@ -1326,7 +1329,7 @@
 
         // Overlay a dark rectangle to dim.
         if (mNeedsToDimEntireKeyboard) {
-            canvas.drawRect(0, 0, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
+            canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
         }
     }
 
@@ -1350,9 +1353,10 @@
         }
     }
 
-    private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
+    private static boolean fitsTextIntoWidth(final int width, final String text,
+            final Paint paint) {
         paint.setTextScaleX(1.0f);
-        final float textWidth = getLabelWidth(text, paint);
+        final float textWidth = TypefaceUtils.getLabelWidth(text, paint);
         if (textWidth < width) {
             return true;
         }
@@ -1363,12 +1367,12 @@
         }
 
         paint.setTextScaleX(scaleX);
-        return getLabelWidth(text, paint) < width;
+        return TypefaceUtils.getLabelWidth(text, paint) < width;
     }
 
     // Layout language name on spacebar.
-    private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype,
-            final int width) {
+    private static String layoutLanguageOnSpacebar(final Paint paint,
+            final InputMethodSubtype subtype, final int width) {
         // Choose appropriate language name to fit into the width.
         final String fullText = getFullDisplayName(subtype);
         if (fitsTextIntoWidth(width, fullText, paint)) {
@@ -1457,7 +1461,7 @@
             return "";
         }
         final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
-        return StringUtils.toTitleCase(locale.getLanguage(), locale);
+        return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
     }
 
     // Get InputMethodSubtype's middle display name in its locale.
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 66c3014..ae08a59 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 
@@ -73,10 +74,11 @@
                 final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
                 final boolean isFixedColumnOrder, final int dividerWidth) {
             mIsFixedOrder = isFixedColumnOrder;
-            if (parentKeyboardWidth / keyWidth < maxColumns) {
+            if (parentKeyboardWidth / keyWidth < Math.min(numKeys, maxColumns)) {
                 throw new IllegalArgumentException(
                         "Keyboard is too small to hold more keys keyboard: "
-                                + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
+                                + parentKeyboardWidth + " " + keyWidth + " "
+                                + numKeys + " " + maxColumns);
             }
             mDefaultKeyWidth = keyWidth;
             mDefaultRowHeight = rowHeight;
@@ -257,7 +259,6 @@
         private static final float LABEL_PADDING_RATIO = 0.2f;
         private static final float DIVIDER_RATIO = 0.2f;
 
-
         /**
          * The builder of MoreKeysKeyboard.
          * @param context the context of {@link MoreKeysKeyboardView}.
@@ -289,11 +290,23 @@
                 // be considered because the vertical positions of both backgrounds were already
                 // adjusted with their bottom paddings deducted.
                 width = keyPreviewDrawParams.mPreviewVisibleWidth;
-                height = keyPreviewDrawParams.mPreviewVisibleHeight
-                        + mParams.mVerticalGap;
+                height = keyPreviewDrawParams.mPreviewVisibleHeight + mParams.mVerticalGap;
+                // TODO: Remove this check.
+                if (width == 0) {
+                    throw new IllegalArgumentException(
+                            "Zero width key detected: " + parentKey + " in " + parentKeyboard.mId);
+                }
             } else {
-                width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth);
+                width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth,
+                        context.getResources());
                 height = parentKeyboard.mMostCommonKeyHeight;
+                // TODO: Remove this check.
+                if (width == 0) {
+                    throw new IllegalArgumentException(
+                            "Zero width calculated: " + parentKey
+                            + " moreKeys=" + java.util.Arrays.toString(parentKey.mMoreKeys)
+                            + " in " + parentKeyboard.mId);
+                }
             }
             final int dividerWidth;
             if (parentKey.needsDividersInMoreKeys()) {
@@ -310,22 +323,18 @@
         }
 
         private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
-                final int minKeyWidth) {
-            final int padding = (int)(view.getResources()
-                    .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
-                    + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
-            final Paint paint = view.newDefaultLabelPaint();
-            paint.setTypeface(parentKey.selectTypeface(view.mKeyDrawParams));
-            paint.setTextSize(parentKey.selectMoreKeyTextSize(view.mKeyDrawParams));
+                final int minKeyWidth, final Resources res) {
+            final float padding =
+                    res.getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
+                    + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0.0f);
+            final Paint paint = view.newLabelPaint(parentKey);
             int maxWidth = minKeyWidth;
             for (final MoreKeySpec spec : parentKey.mMoreKeys) {
                 final String label = spec.mLabel;
                 // If the label is single letter, minKeyWidth is enough to hold the label.
                 if (label != null && StringUtils.codePointCount(label) > 1) {
-                    final int width = (int)view.getLabelWidth(label, paint) + padding;
-                    if (maxWidth < width) {
-                        maxWidth = width;
-                    }
+                    maxWidth = Math.max(maxWidth,
+                            (int)(TypefaceUtils.getLabelWidth(label, paint) + padding));
                 }
             }
             return maxWidth;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 0d42ab2..a356eb1 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -71,7 +71,7 @@
     public void setKeyboard(final Keyboard keyboard) {
         super.setKeyboard(keyboard);
         mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
-                -getPaddingTop() + mVerticalCorrection);
+                -getPaddingTop() + getVerticalCorrection());
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 32cee73..b77e378 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -26,7 +26,7 @@
 
 import java.util.Arrays;
 
-public final class ProximityInfo {
+public class ProximityInfo {
     private static final String TAG = ProximityInfo.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -79,22 +79,21 @@
         mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
     }
 
-    private static ProximityInfo createDummyProximityInfo() {
-        return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
-    }
-
-    public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximityCharsArray,
-            final int rowSize, final int gridWidth, final int gridHeight) {
-        final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
-        spellCheckerProximityInfo.mNativeProximityInfo =
-                spellCheckerProximityInfo.setProximityInfoNative("" /* locale */,
-                        gridWidth /* displayWidth */, gridHeight /* displayHeight */,
-                        gridWidth, gridHeight, 1 /* mostCommonKeyWidth */, proximityCharsArray,
-                        0 /* keyCount */, null /*keyXCoordinates */, null /* keyYCoordinates */,
-                        null /* keyWidths */, null /* keyHeights */, null /* keyCharCodes */,
-                        null /* sweetSpotCenterXs */, null /* sweetSpotCenterYs */,
-                        null /* sweetSpotRadii */);
-        return spellCheckerProximityInfo;
+    /**
+     * Constructor for subclasses such as
+     * {@link com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo}.
+     */
+    protected ProximityInfo(final int[] proximityCharsArray, final int gridWidth,
+            final int gridHeight) {
+        this("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
+        mNativeProximityInfo = setProximityInfoNative("" /* locale */,
+                gridWidth /* displayWidth */, gridHeight /* displayHeight */,
+                gridWidth, gridHeight, 1 /* mostCommonKeyWidth */,
+                1 /* mostCommonKeyHeight */, proximityCharsArray, 0 /* keyCount */,
+                null /*keyXCoordinates */, null /* keyYCoordinates */,
+                null /* keyWidths */, null /* keyHeights */, null /* keyCharCodes */,
+                null /* sweetSpotCenterXs */, null /* sweetSpotCenterYs */,
+                null /* sweetSpotRadii */);
     }
 
     private long mNativeProximityInfo;
@@ -105,9 +104,10 @@
     // TODO: Stop passing proximityCharsArray
     private static native long setProximityInfoNative(String locale,
             int displayWidth, int displayHeight, int gridWidth, int gridHeight,
-            int mostCommonKeyWidth, int[] proximityCharsArray, int keyCount, int[] keyXCoordinates,
-            int[] keyYCoordinates, int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
-            float[] sweetSpotCenterXs, float[] sweetSpotCenterYs, float[] sweetSpotRadii);
+            int mostCommonKeyWidth, int mostCommonKeyHeight, int[] proximityCharsArray,
+            int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, int[] keyWidths,
+            int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs,
+            float[] sweetSpotCenterYs, float[] sweetSpotRadii);
 
     private static native void releaseProximityInfoNative(long nativeProximityInfo);
 
@@ -234,9 +234,9 @@
 
         // TODO: Stop passing proximityCharsArray
         return setProximityInfoNative(mLocaleStr, mKeyboardMinWidth, mKeyboardHeight,
-                mGridWidth, mGridHeight, mMostCommonKeyWidth, proximityCharsArray, keyCount,
-                keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
-                sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
+                mGridWidth, mGridHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
+                proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths,
+                keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
     }
 
     public long getNativeProximityInfo() {
diff --git a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java b/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
new file mode 100644
index 0000000..6a54e11
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.util.SparseArray;
+
+import com.android.inputmethod.latin.CollectionUtils;
+
+public final class TypefaceUtils {
+    private TypefaceUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    // This sparse array caches key label text height in pixel indexed by key label text size.
+    private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
+    // Working variable for the following method.
+    private static final Rect sTextHeightBounds = new Rect();
+
+    public static float getCharHeight(final char[] referenceChar, final Paint paint) {
+        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
+        synchronized (sTextHeightCache) {
+            final Float cachedValue = sTextHeightCache.get(key);
+            if (cachedValue != null) {
+                return cachedValue;
+            }
+
+            paint.getTextBounds(referenceChar, 0, 1, sTextHeightBounds);
+            final float height = sTextHeightBounds.height();
+            sTextHeightCache.put(key, height);
+            return height;
+        }
+    }
+
+    // This sparse array caches key label text width in pixel indexed by key label text size.
+    private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
+    // Working variable for the following method.
+    private static final Rect sTextWidthBounds = new Rect();
+
+    public static float getCharWidth(final char[] referenceChar, final Paint paint) {
+        final int key = getCharGeometryCacheKey(referenceChar[0], paint);
+        synchronized (sTextWidthCache) {
+            final Float cachedValue = sTextWidthCache.get(key);
+            if (cachedValue != null) {
+                return cachedValue;
+            }
+
+            paint.getTextBounds(referenceChar, 0, 1, sTextWidthBounds);
+            final float width = sTextWidthBounds.width();
+            sTextWidthCache.put(key, width);
+            return width;
+        }
+    }
+
+    private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
+        final int labelSize = (int)paint.getTextSize();
+        final Typeface face = paint.getTypeface();
+        final int codePointOffset = referenceChar << 15;
+        if (face == Typeface.DEFAULT) {
+            return codePointOffset + labelSize;
+        } else if (face == Typeface.DEFAULT_BOLD) {
+            return codePointOffset + labelSize + 0x1000;
+        } else if (face == Typeface.MONOSPACE) {
+            return codePointOffset + labelSize + 0x2000;
+        } else {
+            return codePointOffset + labelSize;
+        }
+    }
+
+    public static float getLabelWidth(final String label, final Paint paint) {
+        final Rect textBounds = new Rect();
+        paint.getTextBounds(label, 0, label.length(), textBounds);
+        return textBounds.width();
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index b047fe0..f682b51 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -37,6 +37,7 @@
 final class GesturePreviewTrail {
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
 
+    // These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}.
     private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
@@ -44,6 +45,7 @@
     // The wall time of the zero value in {@link #mEventTimes}
     private long mCurrentTimeBase;
     private int mTrailStartIndex;
+    private int mLastInterpolatedDrawIndex;
 
     static final class Params {
         public final int mTrailColor;
@@ -89,13 +91,30 @@
     }
 
     public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
-        final int trailSize = mEventTimes.getLength();
+        synchronized (mEventTimes) {
+            addStrokeLocked(stroke, downTime);
+        }
+    }
+
+    private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
+            final int trailSize = mEventTimes.getLength();
         stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
         if (mEventTimes.getLength() == trailSize) {
             return;
         }
         final int[] eventTimes = mEventTimes.getPrimitiveArray();
         final int strokeId = stroke.getGestureStrokeId();
+        // Because interpolation algorithm in {@link GestureStrokeWithPreviewPoints} can't determine
+        // the interpolated points in the last segment of gesture stroke, it may need recalculation
+        // of interpolation when new segments are added to the stroke.
+        // {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may
+        // be updated by the interpolation
+        // {@link GestureStrokeWithPreviewPoints#interpolatePreviewStroke}
+        // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,Params)} below.
+        final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId)
+                ? mLastInterpolatedDrawIndex : trailSize;
+        mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment(
+                lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates);
         if (strokeId != mCurrentStrokeId) {
             final int elapsedTime = (int)(downTime - mCurrentTimeBase);
             for (int i = mTrailStartIndex; i < trailSize; i++) {
@@ -157,6 +176,13 @@
      */
     public boolean drawGestureTrail(final Canvas canvas, final Paint paint,
             final Rect outBoundsRect, final Params params) {
+        synchronized (mEventTimes) {
+            return drawGestureTrailLocked(canvas, paint, outBoundsRect, params);
+        }
+    }
+
+    private boolean drawGestureTrailLocked(final Canvas canvas, final Paint paint,
+            final Rect outBoundsRect, final Params params) {
         // Initialize bounds rectangle.
         outBoundsRect.setEmpty();
         final int trailSize = mEventTimes.getLength();
@@ -216,6 +242,10 @@
                 System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
                 System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
                 System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+                // The start index of the last segment of the stroke
+                // {@link mLastInterpolatedDrawIndex} should also be updated because all array
+                // elements have just been shifted for compaction.
+                mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0);
             }
             mEventTimes.setLength(newSize);
             mXCoordinates.setLength(newSize);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index fc81410..3315954 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -21,19 +21,32 @@
 public final class GestureStrokeWithPreviewPoints extends GestureStroke {
     public static final int PREVIEW_CAPACITY = 256;
 
+    private static final boolean ENABLE_INTERPOLATION = true;
+
     private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
     private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
     private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
 
     private int mStrokeId;
     private int mLastPreviewSize;
+    private final HermiteInterpolator mInterpolator = new HermiteInterpolator();
+    private int mLastInterpolatedPreviewIndex;
 
-    private int mMinPreviewSampleLengthSquare;
+    private int mMinPreviewSamplingDistanceSquared;
     private int mLastX;
     private int mLastY;
+    private double mMinPreviewSamplingDistance;
+    private double mDistanceFromLastSample;
 
-    // TODO: Move this to resource.
-    private static final float MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH = 0.1f;
+    // TODO: Move these constants to resource.
+    // The minimum linear distance between sample points for preview in keyWidth unit.
+    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.1f;
+    // The minimum trail distance between sample points for preview in keyWidth unit when using
+    // interpolation.
+    private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION = 0.2f;
+    // The angular threshold to use interpolation in radian. PI/12 is 15 degree.
+    private static final double INTERPOLATION_ANGULAR_THRESHOLD = Math.PI / 12.0d;
+    private static final int MAX_INTERPOLATION_PARTITION = 4;
 
     public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) {
         super(pointerId, params);
@@ -44,6 +57,7 @@
         super.reset();
         mStrokeId++;
         mLastPreviewSize = 0;
+        mLastInterpolatedPreviewIndex = 0;
         mPreviewEventTimes.setLength(0);
         mPreviewXCoordinates.setLength(0);
         mPreviewYCoordinates.setLength(0);
@@ -53,35 +67,49 @@
         return mStrokeId;
     }
 
-    public int getGestureStrokePreviewSize() {
-        return mPreviewEventTimes.getLength();
-    }
-
     @Override
     public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
         super.setKeyboardGeometry(keyWidth, keyboardHeight);
-        final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH;
-        mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength);
+        final float samplingRatioToKeyWidth = ENABLE_INTERPOLATION
+                ? MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION
+                : MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH;
+        mMinPreviewSamplingDistance = keyWidth * samplingRatioToKeyWidth;
+        mMinPreviewSamplingDistanceSquared = (int)(
+                mMinPreviewSamplingDistance * mMinPreviewSamplingDistance);
     }
 
-    private boolean needsSampling(final int x, final int y) {
+    private boolean needsSampling(final int x, final int y, final boolean isMajorEvent) {
+        if (ENABLE_INTERPOLATION) {
+            mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
+            mLastX = x;
+            mLastY = y;
+            if (mDistanceFromLastSample >= mMinPreviewSamplingDistance) {
+                mDistanceFromLastSample = 0.0d;
+                return true;
+            }
+            return false;
+        }
+
         final int dx = x - mLastX;
         final int dy = y - mLastY;
-        return dx * dx + dy * dy >= mMinPreviewSampleLengthSquare;
+        if (isMajorEvent || dx * dx + dy * dy >= mMinPreviewSamplingDistanceSquared) {
+            mLastX = x;
+            mLastY = y;
+            return true;
+        }
+        return false;
     }
 
     @Override
     public boolean addPointOnKeyboard(final int x, final int y, final int time,
             final boolean isMajorEvent) {
-        final boolean onValidArea = super.addPointOnKeyboard(x, y, time, isMajorEvent);
-        if (isMajorEvent || needsSampling(x, y)) {
+        if (needsSampling(x, y, isMajorEvent)) {
             mPreviewEventTimes.add(time);
             mPreviewXCoordinates.add(x);
             mPreviewYCoordinates.add(y);
-            mLastX = x;
-            mLastY = y;
         }
-        return onValidArea;
+        return super.addPointOnKeyboard(x, y, time, isMajorEvent);
+
     }
 
     public void appendPreviewStroke(final ResizableIntArray eventTimes,
@@ -95,4 +123,82 @@
         yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
         mLastPreviewSize = mPreviewEventTimes.getLength();
     }
+
+    /**
+     * Calculate interpolated points between the last interpolated point and the end of the trail.
+     * And return the start index of the last interpolated segment of input arrays because it
+     * may need to recalculate the interpolated points in the segment if further segments are
+     * added to this stroke.
+     *
+     * @param lastInterpolatedIndex the start index of the last interpolated segment of
+     *        <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>.
+     * @param eventTimes the event time array of gesture preview trail to be drawn.
+     * @param xCoords the x-coordinates array of gesture preview trail to be drawn.
+     * @param yCoords the y-coordinates array of gesture preview trail to be drawn.
+     * @return the start index of the last interpolated segment of input arrays.
+     */
+    public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
+            final ResizableIntArray eventTimes, final ResizableIntArray xCoords,
+            final ResizableIntArray yCoords) {
+        if (!ENABLE_INTERPOLATION) {
+            return lastInterpolatedIndex;
+        }
+        final int size = mPreviewEventTimes.getLength();
+        final int[] pt = mPreviewEventTimes.getPrimitiveArray();
+        final int[] px = mPreviewXCoordinates.getPrimitiveArray();
+        final int[] py = mPreviewYCoordinates.getPrimitiveArray();
+        mInterpolator.reset(px, py, 0, size);
+        // The last segment of gesture stroke needs to be interpolated again because the slope of
+        // the tangent at the last point isn't determined.
+        int lastInterpolatedDrawIndex = lastInterpolatedIndex;
+        int d1 = lastInterpolatedIndex;
+        for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) {
+            final int p1 = p2 - 1;
+            final int p0 = p1 - 1;
+            final int p3 = p2 + 1;
+            mLastInterpolatedPreviewIndex = p1;
+            lastInterpolatedDrawIndex = d1;
+            mInterpolator.setInterval(p0, p1, p2, p3);
+            final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X);
+            final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X);
+            final double dm = Math.abs(angularDiff(m2, m1));
+            final int partition = Math.min((int)Math.ceil(dm / INTERPOLATION_ANGULAR_THRESHOLD),
+                    MAX_INTERPOLATION_PARTITION);
+            final int t1 = eventTimes.get(d1);
+            final int dt = pt[p2] - pt[p1];
+            d1++;
+            for (int i = 1; i < partition; i++) {
+                final float t = i / (float)partition;
+                mInterpolator.interpolate(t);
+                eventTimes.add(d1, (int)(dt * t) + t1);
+                xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
+                yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
+                d1++;
+            }
+            eventTimes.add(d1, pt[p2]);
+            xCoords.add(d1, px[p2]);
+            yCoords.add(d1, py[p2]);
+        }
+        return lastInterpolatedDrawIndex;
+    }
+
+    private static final double TWO_PI = Math.PI * 2.0d;
+
+    /**
+     * Calculate the angular of rotation from <code>a0</code> to <code>a1</code>.
+     *
+     * @param a1 the angular to which the rotation ends.
+     * @param a0 the angular from which the rotation starts.
+     * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI].
+     */
+    private static double angularDiff(final double a1, final double a0) {
+        double deltaAngle = a1 - a0;
+        while (deltaAngle > Math.PI) {
+            deltaAngle -= TWO_PI;
+        }
+        while (deltaAngle < -Math.PI) {
+            deltaAngle += TWO_PI;
+        }
+        return deltaAngle;
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
new file mode 100644
index 0000000..0ec8153
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+/**
+ * Interpolates XY-coordinates using Cubic Hermite Curve.
+ */
+public final class HermiteInterpolator {
+    private int[] mXCoords;
+    private int[] mYCoords;
+    private int mMinPos;
+    private int mMaxPos;
+
+    // Working variable to calculate interpolated value.
+    /** The coordinates of the start point of the interval. */
+    public int mP1X, mP1Y;
+    /** The coordinates of the end point of the interval. */
+    public int mP2X, mP2Y;
+    /** The slope of the tangent at the start point. */
+    public float mSlope1X, mSlope1Y;
+    /** The slope of the tangent at the end point. */
+    public float mSlope2X, mSlope2Y;
+    /** The interpolated coordinates.
+     * The return variables of {@link #interpolate(float)} to avoid instantiations.
+     */
+    public float mInterpolatedX, mInterpolatedY;
+
+    public HermiteInterpolator() {
+        // Nothing to do with here.
+    }
+
+    /**
+     * Reset this interpolator to point XY-coordinates data.
+     * @param xCoords the array of x-coordinates. Valid data are in left-open interval
+     *                <code>[minPos, maxPos)</code>.
+     * @param yCoords the array of y-coordinates. Valid data are in left-open interval
+     *                <code>[minPos, maxPos)</code>.
+     * @param minPos the minimum index of left-open interval of valid data.
+     * @param maxPos the maximum index of left-open interval of valid data.
+     */
+    @UsedForTesting
+    public void reset(final int[] xCoords, final int[] yCoords, final int minPos,
+            final int maxPos) {
+        mXCoords = xCoords;
+        mYCoords = yCoords;
+        mMinPos = minPos;
+        mMaxPos = maxPos;
+    }
+
+    /**
+     * Set interpolation interval.
+     * <p>
+     * The start and end coordinates of the interval will be set in {@link #mP1X}, {@link #mP1Y},
+     * {@link #mP2X}, and {@link #mP2Y}. The slope of the tangents at start and end points will be
+     * set in {@link #mSlope1X}, {@link #mSlope1Y}, {@link #mSlope2X}, and {@link #mSlope2Y}.
+     *
+     * @param p0 the index just before interpolation interval. If <code>p1</code> points the start
+     *           of valid points, <code>p0</code> must be less than <code>minPos</code> of
+     *           {@link #reset(int[],int[],int,int)}.
+     * @param p1 the start index of interpolation interval.
+     * @param p2 the end index of interpolation interval.
+     * @param p3 the index just after interpolation interval. If <code>p2</code> points the end of
+     *           valid points, <code>p3</code> must be equal or greater than <code>maxPos</code> of
+     *           {@link #reset(int[],int[],int,int)}.
+     */
+    @UsedForTesting
+    public void setInterval(final int p0, final int p1, final int p2, final int p3) {
+        mP1X = mXCoords[p1];
+        mP1Y = mYCoords[p1];
+        mP2X = mXCoords[p2];
+        mP2Y = mYCoords[p2];
+        // A(ax,ay) is the vector p1->p2.
+        final int ax = mP2X - mP1X;
+        final int ay = mP2Y - mP1Y;
+
+        // Calculate the slope of the tangent at p1.
+        if (p0 >= mMinPos) {
+            // p1 has previous valid point p0.
+            // The slope of the tangent is half of the vector p0->p2.
+            mSlope1X = (mP2X - mXCoords[p0]) / 2.0f;
+            mSlope1Y = (mP2Y - mYCoords[p0]) / 2.0f;
+        } else if (p3 < mMaxPos) {
+            // p1 has no previous valid point, but p2 has next valid point p3.
+            // B(bx,by) is the slope vector of the tangent at p2.
+            final float bx = (mXCoords[p3] - mP1X) / 2.0f;
+            final float by = (mYCoords[p3] - mP1Y) / 2.0f;
+            final float crossProdAB = ax * by - ay * bx;
+            final float dotProdAB = ax * bx + ay * by;
+            final float normASquare = ax * ax + ay * ay;
+            final float invHalfNormASquare = 1.0f / normASquare / 2.0f;
+            // The slope of the tangent is the mirror image of vector B to vector A.
+            mSlope1X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay);
+            mSlope1Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax);
+        } else {
+            // p1 and p2 have no previous valid point. (Interval has only point p1 and p2)
+            mSlope1X = ax;
+            mSlope1Y = ay;
+        }
+
+        // Calculate the slope of the tangent at p2.
+        if (p3 < mMaxPos) {
+            // p2 has next valid point p3.
+            // The slope of the tangent is half of the vector p1->p3.
+            mSlope2X = (mXCoords[p3] - mP1X) / 2.0f;
+            mSlope2Y = (mYCoords[p3] - mP1Y) / 2.0f;
+        } else if (p0 >= mMinPos) {
+            // p2 has no next valid point, but p1 has previous valid point p0.
+            // B(bx,by) is the slope vector of the tangent at p1.
+            final float bx = (mP2X - mXCoords[p0]) / 2.0f;
+            final float by = (mP2Y - mYCoords[p0]) / 2.0f;
+            final float crossProdAB = ax * by - ay * bx;
+            final float dotProdAB = ax * bx + ay * by;
+            final float normASquare = ax * ax + ay * ay;
+            final float invHalfNormASquare = 1.0f / normASquare / 2.0f;
+            // The slope of the tangent is the mirror image of vector B to vector A.
+            mSlope2X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay);
+            mSlope2Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax);
+        } else {
+            // p1 and p2 has no previous valid point. (Interval has only point p1 and p2)
+            mSlope2X = ax;
+            mSlope2Y = ay;
+        }
+    }
+
+    /**
+     * Calculate interpolation value at <code>t</code> in unit interval <code>[0,1]</code>.
+     * <p>
+     * On the unit interval [0,1], given a starting point p1 at t=0 and an ending point p2 at t=1
+     * with the slope of the tangent m1 at p1 and m2 at p2, the polynomial of cubic Hermite curve
+     * can be defined by
+     *   p(t) = (1+2t)(1-t)(1-t)*p1 + t(1-t)(1-t)*m1 + (3-2t)t^2*p2 + (t-1)t^2*m2
+     * where t is an element of [0,1].
+     * <p>
+     * The interpolated XY-coordinates will be set in {@link #mInterpolatedX} and
+     * {@link #mInterpolatedY}.
+     *
+     * @param t the interpolation parameter. The value must be in close interval <code>[0,1]</code>.
+     */
+    @UsedForTesting
+    public void interpolate(final float t) {
+        final float omt = 1.0f - t;
+        final float tm2 = 2.0f * t;
+        final float k1 = 1.0f + tm2;
+        final float k2 = 3.0f - tm2;
+        final float omt2 = omt * omt;
+        final float t2 = t * t;
+        mInterpolatedX = (k1 * mP1X + t * mSlope1X) * omt2 + (k2 * mP2X - omt * mSlope2X) * t2;
+        mInterpolatedY = (k1 * mP1Y + t * mSlope1Y) * omt2 + (k2 * mP2Y - omt * mSlope2Y) * t2;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 2df7e5c..6bc6acc 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -34,175 +34,197 @@
     }
 
     private static final int INITIAL_CAPACITY = 10;
+    // Note: {@link #mExpandableArrayOfActivePointers} and {@link #mArraySize} are synchronized by
+    // {@link #mExpandableArrayOfActivePointers}
     private final ArrayList<Element> mExpandableArrayOfActivePointers =
             CollectionUtils.newArrayList(INITIAL_CAPACITY);
     private int mArraySize = 0;
 
-    public synchronized int size() {
-        return mArraySize;
-    }
-
-    public synchronized void add(final Element pointer) {
-        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
-        final int arraySize = mArraySize;
-        if (arraySize < expandableArray.size()) {
-            expandableArray.set(arraySize, pointer);
-        } else {
-            expandableArray.add(pointer);
+    public int size() {
+        synchronized (mExpandableArrayOfActivePointers) {
+            return mArraySize;
         }
-        mArraySize = arraySize + 1;
     }
 
-    public synchronized void remove(final Element pointer) {
-        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
-        final int arraySize = mArraySize;
-        int newSize = 0;
-        for (int index = 0; index < arraySize; index++) {
-            final Element element = expandableArray.get(index);
-            if (element == pointer) {
+    public void add(final Element pointer) {
+        synchronized (mExpandableArrayOfActivePointers) {
+            final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+            final int arraySize = mArraySize;
+            if (arraySize < expandableArray.size()) {
+                expandableArray.set(arraySize, pointer);
+            } else {
+                expandableArray.add(pointer);
+            }
+            mArraySize = arraySize + 1;
+        }
+    }
+
+    public void remove(final Element pointer) {
+        synchronized (mExpandableArrayOfActivePointers) {
+            final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+            final int arraySize = mArraySize;
+            int newSize = 0;
+            for (int index = 0; index < arraySize; index++) {
+                final Element element = expandableArray.get(index);
+                if (element == pointer) {
+                    if (newSize != index) {
+                        Log.w(TAG, "Found duplicated element in remove: " + pointer);
+                    }
+                    continue; // Remove this element from the expandableArray.
+                }
                 if (newSize != index) {
-                    Log.w(TAG, "Found duplicated element in remove: " + pointer);
+                    // Shift this element toward the beginning of the expandableArray.
+                    expandableArray.set(newSize, element);
                 }
-                continue; // Remove this element from the expandableArray.
-            }
-            if (newSize != index) {
-                // Shift this element toward the beginning of the expandableArray.
-                expandableArray.set(newSize, element);
-            }
-            newSize++;
-        }
-        mArraySize = newSize;
-    }
-
-    public synchronized Element getOldestElement() {
-        return (mArraySize == 0) ? null : mExpandableArrayOfActivePointers.get(0);
-    }
-
-    public synchronized void releaseAllPointersOlderThan(final Element pointer,
-            final long eventTime) {
-        if (DEBUG) {
-            Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
-        }
-        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
-        final int arraySize = mArraySize;
-        int newSize, index;
-        for (newSize = index = 0; index < arraySize; index++) {
-            final Element element = expandableArray.get(index);
-            if (element == pointer) {
-                break; // Stop releasing elements.
-            }
-            if (!element.isModifier()) {
-                element.onPhantomUpEvent(eventTime);
-                continue; // Remove this element from the expandableArray.
-            }
-            if (newSize != index) {
-                // Shift this element toward the beginning of the expandableArray.
-                expandableArray.set(newSize, element);
-            }
-            newSize++;
-        }
-        // Shift rest of the expandableArray.
-        int count = 0;
-        for (; index < arraySize; index++) {
-            final Element element = expandableArray.get(index);
-            if (element == pointer) {
-                if (count > 0) {
-                    Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
-                            + pointer);
-                }
-                count++;
-            }
-            if (newSize != index) {
-                expandableArray.set(newSize, expandableArray.get(index));
                 newSize++;
             }
+            mArraySize = newSize;
         }
-        mArraySize = newSize;
+    }
+
+    public Element getOldestElement() {
+        synchronized (mExpandableArrayOfActivePointers) {
+            return (mArraySize == 0) ? null : mExpandableArrayOfActivePointers.get(0);
+        }
+    }
+
+    public void releaseAllPointersOlderThan(final Element pointer, final long eventTime) {
+        synchronized (mExpandableArrayOfActivePointers) {
+            if (DEBUG) {
+                Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
+            }
+            final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+            final int arraySize = mArraySize;
+            int newSize, index;
+            for (newSize = index = 0; index < arraySize; index++) {
+                final Element element = expandableArray.get(index);
+                if (element == pointer) {
+                    break; // Stop releasing elements.
+                }
+                if (!element.isModifier()) {
+                    element.onPhantomUpEvent(eventTime);
+                    continue; // Remove this element from the expandableArray.
+                }
+                if (newSize != index) {
+                    // Shift this element toward the beginning of the expandableArray.
+                    expandableArray.set(newSize, element);
+                }
+                newSize++;
+            }
+            // Shift rest of the expandableArray.
+            int count = 0;
+            for (; index < arraySize; index++) {
+                final Element element = expandableArray.get(index);
+                if (element == pointer) {
+                    if (count > 0) {
+                        Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
+                                + pointer);
+                    }
+                    count++;
+                }
+                if (newSize != index) {
+                    expandableArray.set(newSize, expandableArray.get(index));
+                    newSize++;
+                }
+            }
+            mArraySize = newSize;
+        }
     }
 
     public void releaseAllPointers(final long eventTime) {
         releaseAllPointersExcept(null, eventTime);
     }
 
-    public synchronized void releaseAllPointersExcept(final Element pointer,
-            final long eventTime) {
-        if (DEBUG) {
-            if (pointer == null) {
-                Log.d(TAG, "releaseAllPoniters: " + this);
-            } else {
-                Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
-            }
-        }
-        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
-        final int arraySize = mArraySize;
-        int newSize = 0, count = 0;
-        for (int index = 0; index < arraySize; index++) {
-            final Element element = expandableArray.get(index);
-            if (element == pointer) {
-                if (count > 0) {
-                    Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " + pointer);
+    public void releaseAllPointersExcept(final Element pointer, final long eventTime) {
+        synchronized (mExpandableArrayOfActivePointers) {
+            if (DEBUG) {
+                if (pointer == null) {
+                    Log.d(TAG, "releaseAllPoniters: " + this);
+                } else {
+                    Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
                 }
-                count++;
-            } else {
-                element.onPhantomUpEvent(eventTime);
-                continue; // Remove this element from the expandableArray.
             }
-            if (newSize != index) {
-                // Shift this element toward the beginning of the expandableArray.
-                expandableArray.set(newSize, element);
+            final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+            final int arraySize = mArraySize;
+            int newSize = 0, count = 0;
+            for (int index = 0; index < arraySize; index++) {
+                final Element element = expandableArray.get(index);
+                if (element == pointer) {
+                    if (count > 0) {
+                        Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: "
+                                + pointer);
+                    }
+                    count++;
+                } else {
+                    element.onPhantomUpEvent(eventTime);
+                    continue; // Remove this element from the expandableArray.
+                }
+                if (newSize != index) {
+                    // Shift this element toward the beginning of the expandableArray.
+                    expandableArray.set(newSize, element);
+                }
+                newSize++;
             }
-            newSize++;
+            mArraySize = newSize;
         }
-        mArraySize = newSize;
     }
 
-    public synchronized boolean hasModifierKeyOlderThan(final Element pointer) {
-        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
-        final int arraySize = mArraySize;
-        for (int index = 0; index < arraySize; index++) {
-            final Element element = expandableArray.get(index);
-            if (element == pointer) {
-                return false; // Stop searching modifier key.
+    public boolean hasModifierKeyOlderThan(final Element pointer) {
+        synchronized (mExpandableArrayOfActivePointers) {
+            final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+            final int arraySize = mArraySize;
+            for (int index = 0; index < arraySize; index++) {
+                final Element element = expandableArray.get(index);
+                if (element == pointer) {
+                    return false; // Stop searching modifier key.
+                }
+                if (element.isModifier()) {
+                    return true;
+                }
             }
-            if (element.isModifier()) {
-                return true;
-            }
+            return false;
         }
-        return false;
     }
 
-    public synchronized boolean isAnyInSlidingKeyInput() {
-        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
-        final int arraySize = mArraySize;
-        for (int index = 0; index < arraySize; index++) {
-            final Element element = expandableArray.get(index);
-            if (element.isInSlidingKeyInput()) {
-                return true;
+    public boolean isAnyInSlidingKeyInput() {
+        synchronized (mExpandableArrayOfActivePointers) {
+            final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+            final int arraySize = mArraySize;
+            for (int index = 0; index < arraySize; index++) {
+                final Element element = expandableArray.get(index);
+                if (element.isInSlidingKeyInput()) {
+                    return true;
+                }
             }
+            return false;
         }
-        return false;
     }
 
-    public synchronized void cancelAllPointerTracker() {
-        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
-        final int arraySize = mArraySize;
-        for (int index = 0; index < arraySize; index++) {
-            final Element element = expandableArray.get(index);
-            element.cancelTracking();
+    public void cancelAllPointerTracker() {
+        synchronized (mExpandableArrayOfActivePointers) {
+            final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+            final int arraySize = mArraySize;
+            for (int index = 0; index < arraySize; index++) {
+                final Element element = expandableArray.get(index);
+                element.cancelTracking();
+            }
         }
     }
 
     @Override
-    public synchronized String toString() {
-        final StringBuilder sb = new StringBuilder();
-        final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
-        final int arraySize = mArraySize;
-        for (int index = 0; index < arraySize; index++) {
-            final Element element = expandableArray.get(index);
-            if (sb.length() > 0)
-                sb.append(" ");
-            sb.append(element.toString());
+    public String toString() {
+        synchronized (mExpandableArrayOfActivePointers) {
+            final StringBuilder sb = new StringBuilder();
+            final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+            final int arraySize = mArraySize;
+            for (int index = 0; index < arraySize; index++) {
+                final Element element = expandableArray.get(index);
+                if (sb.length() > 0) {
+                    sb.append(" ");
+                }
+                sb.append(element.toString());
+            }
+            return "[" + sb.toString() + "]";
         }
-        return "[" + sb.toString() + "]";
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 4bec99c..42f7136 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -422,7 +422,7 @@
 
     private static void reinitializeClientRecordInDictionaryContentProvider(final Context context,
             final ContentProviderClient client, final String clientId) throws RemoteException {
-        final String metadataFileUri = context.getString(R.string.dictionary_pack_metadata_uri);
+        final String metadataFileUri = MetadataFileUriGetter.getMetadataUri(context);
         if (TextUtils.isEmpty(metadataFileUri)) return;
         // Tell the content provider to reset all information about this client id
         final Uri metadataContentUri = getProviderUriBuilder(clientId)
@@ -450,4 +450,25 @@
                     info.toContentValues());
         }
     }
+
+    /**
+     * Initialize a client record with the dictionary content provider.
+     *
+     * This merely acquires the content provider and calls
+     * #reinitializeClientRecordInDictionaryContentProvider.
+     *
+     * @param context the context for resources and providers.
+     * @param clientId the client ID to use.
+     */
+    public static void initializeClientRecordHelper(final Context context,
+            final String clientId) {
+        try {
+            final ContentProviderClient client = context.getContentResolver().
+                    acquireContentProviderClient(getProviderUriBuilder("").build());
+            if (null == client) return;
+            reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Cannot contact the dictionary content provider", e);
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
index 1012cd5..4b8d1ac 100644
--- a/java/src/com/android/inputmethod/latin/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
@@ -41,7 +41,7 @@
         if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) {
             return s.toUpperCase(locale);
         } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) {
-            return StringUtils.toTitleCase(s, locale);
+            return StringUtils.capitalizeFirstCodePoint(s, locale);
         } else {
             return s;
         }
diff --git a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
new file mode 100644
index 0000000..792a446
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.TextUtils;
+import android.view.inputmethod.CompletionInfo;
+
+import java.util.Arrays;
+
+/**
+ * Utilities to do various stuff with CompletionInfo.
+ */
+public class CompletionInfoUtils {
+    private CompletionInfoUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static CompletionInfo[] removeNulls(final CompletionInfo[] src) {
+        int j = 0;
+        final CompletionInfo[] dst = new CompletionInfo[src.length];
+        for (int i = 0; i < src.length; ++i) {
+            if (null != src[i] && !TextUtils.isEmpty(src[i].getText())) {
+                dst[j] = src[i];
+                ++j;
+            }
+        }
+        return Arrays.copyOfRange(dst, 0, j);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 8b5a76a..22d1899 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -173,7 +173,8 @@
                 // capitalization of i.
                 final int wordLen = StringUtils.codePointCount(word);
                 if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
-                    super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS);
+                    super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
+                            false /* isNotAWord */);
                     if (!TextUtils.isEmpty(prevWord)) {
                         if (mUseFirstLastBigrams) {
                             super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM);
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index ff3d83f..9691fa2 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -37,6 +37,8 @@
     public static final String TYPE_USER = "user";
     // User history dictionary internal to LatinIME.
     public static final String TYPE_USER_HISTORY = "history";
+    // Spawned by resuming suggestions. Comes from a span that was in the TextView.
+    public static final String TYPE_RESUMED = "resumed";
     protected final String mDictType;
 
     public Dictionary(final String dictType) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index 35f3119..41fcb83 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -25,14 +25,35 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.net.Uri;
+import android.util.Log;
 
 /**
- * Takes action to reload the necessary data when a dictionary pack was added/removed.
+ * Receives broadcasts pertaining to dictionary management and takes the appropriate action.
+ *
+ * This object receives three types of broadcasts.
+ * - Package installed/added. When a dictionary provider application is added or removed, we
+ * need to query the dictionaries.
+ * - New dictionary broadcast. The dictionary provider broadcasts new dictionary availability. When
+ * this happens, we need to re-query the dictionaries.
+ * - Unknown client. If the dictionary provider is in urgent need of data about some client that
+ * it does not know, it sends this broadcast. When we receive this, we need to tell the dictionary
+ * provider about ourselves. This happens when the settings for the dictionary pack are accessed,
+ * but Latin IME never got a chance to register itself.
  */
 public final class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = DictionaryPackInstallBroadcastReceiver.class.getSimpleName();
 
     final LatinIME mService;
 
+    public DictionaryPackInstallBroadcastReceiver() {
+        // This empty constructor is necessary for the system to instantiate this receiver.
+        // This happens when the dictionary pack says it can't find a record for our client,
+        // which happens when the dictionary pack settings are called before the keyboard
+        // was ever started once.
+        Log.i(TAG, "Latin IME dictionary broadcast receiver instantiated from the framework.");
+        mService = null;
+    }
+
     public DictionaryPackInstallBroadcastReceiver(final LatinIME service) {
         mService = service;
     }
@@ -44,6 +65,11 @@
 
         // We need to reread the dictionary if a new dictionary package is installed.
         if (action.equals(Intent.ACTION_PACKAGE_ADDED)) {
+            if (null == mService) {
+                Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+                        + "should never happen");
+                return;
+            }
             final Uri packageUri = intent.getData();
             if (null == packageUri) return; // No package name : we can't do anything
             final String packageName = packageUri.getSchemeSpecificPart();
@@ -71,6 +97,11 @@
             return;
         } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
                 && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+            if (null == mService) {
+                Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+                        + "should never happen");
+                return;
+            }
             // When the dictionary package is removed, we need to reread dictionary (to use the
             // next-priority one, or stop using a dictionary at all if this was the only one,
             // since this is the user request).
@@ -82,7 +113,28 @@
             // read dictionary from?
             mService.resetSuggestMainDict();
         } else if (action.equals(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)) {
+            if (null == mService) {
+                Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+                        + "should never happen");
+                return;
+            }
             mService.resetSuggestMainDict();
+        } else if (action.equals(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT)) {
+            if (null != mService) {
+                // Careful! This is returning if the service is NOT null. This is because we
+                // should come here instantiated by the framework in reaction to a broadcast of
+                // the above action, so we should gave gone through the no-args constructor.
+                Log.e(TAG, "Called with intent " + action + " but we have a reference to the "
+                        + "service: this should never happen");
+                return;
+            }
+            // The dictionary provider does not know about some client. We check that it's really
+            // us that it needs to know about, and if it's the case, we register with the provider.
+            final String wantedClientId =
+                    intent.getStringExtra(DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA);
+            final String myClientId = context.getString(R.string.dictionary_pack_client_id);
+            if (!wantedClientId.equals(myClientId)) return; // Not for us
+            BinaryDictionaryFileDumper.initializeClientRecordHelper(context, myClientId);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 97dc6a8..4b1975a 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -176,14 +176,15 @@
      */
     // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
     // considering performance regression.
-    protected void addWord(final String word, final String shortcutTarget, final int frequency) {
+    protected void addWord(final String word, final String shortcutTarget, final int frequency,
+            final boolean isNotAWord) {
         if (shortcutTarget == null) {
-            mFusionDictionary.add(word, frequency, null, false /* isNotAWord */);
+            mFusionDictionary.add(word, frequency, null, isNotAWord);
         } else {
             // TODO: Do this in the subclass, with this class taking an arraylist.
             final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
             shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
-            mFusionDictionary.add(word, frequency, shortcutTargets, false /* isNotAWord */);
+            mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index ae2ee57..fd81d13 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -31,6 +32,7 @@
  * be searched for suggestions and valid words.
  */
 public class ExpandableDictionary extends Dictionary {
+    private static final String TAG = ExpandableDictionary.class.getSimpleName();
     /**
      * The weight to give to a word if it's length is the same as the number of typed characters.
      */
@@ -551,8 +553,13 @@
         // word. We do want however to return the correct case for the right hand side.
         // So we want to squash the case of the left hand side, and preserve that of the right
         // hand side word.
-        Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
-        Node secondWord = searchWord(mRoots, word2, 0, null);
+        final String word1Lower = word1.toLowerCase();
+        if (TextUtils.isEmpty(word1Lower) || TextUtils.isEmpty(word2)) {
+            Log.e(TAG, "Invalid bigram pair: " + word1 + ", " + word1Lower + ", " + word2);
+            return frequency;
+        }
+        final Node firstWord = searchWord(mRoots, word1Lower, 0, null);
+        final Node secondWord = searchWord(mRoots, word2, 0, null);
         LinkedList<NextWord> bigrams = firstWord.mNGrams;
         if (bigrams == null || bigrams.size() == 0) {
             firstWord.mNGrams = CollectionUtils.newLinkedList();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 7bd0981..094ccd7 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -44,7 +44,9 @@
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.text.InputType;
+import android.text.SpannableString;
 import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
 import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -72,6 +74,8 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.RichInputConnection.Range;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.Utils.Stats;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
@@ -196,6 +200,7 @@
         private static final int MSG_PENDING_IMS_CALLBACK = 1;
         private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
         private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
+        private static final int MSG_RESUME_SUGGESTIONS = 4;
 
         private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
 
@@ -233,6 +238,9 @@
                 latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj,
                         msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
                 break;
+            case MSG_RESUME_SUGGESTIONS:
+                latinIme.restartSuggestionsOnWordTouchedByCursor();
+                break;
             }
         }
 
@@ -240,6 +248,10 @@
             sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions);
         }
 
+        public void postResumeSuggestions() {
+            sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
+        }
+
         public void cancelUpdateSuggestionStrip() {
             removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
         }
@@ -803,10 +815,6 @@
 
     @Override
     public void onWindowHidden() {
-        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.latinIME_onWindowHidden(mLastSelectionStart, mLastSelectionEnd,
-                    getCurrentInputConnection());
-        }
         super.onWindowHidden();
         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
@@ -834,8 +842,10 @@
         // Remove pending messages related to update suggestions
         mHandler.cancelUpdateSuggestionStrip();
         resetComposingState(true /* alsoResetLastComposedWord */);
+        // Notify ResearchLogger
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.getInstance().latinIME_onFinishInputViewInternal();
+            ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart,
+                    mLastSelectionEnd, getCurrentInputConnection());
         }
     }
 
@@ -911,13 +921,12 @@
                 resetEntireInputState(newSelStart);
             }
 
+            // We moved the cursor. If we are touching a word, we need to resume suggestion.
+            mHandler.postResumeSuggestions();
+
             mKeyboardSwitcher.updateShiftState();
         }
         mExpectingUpdateSelection = false;
-        // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
-        // here. It would probably be too expensive to call directly here but we may want to post a
-        // message to delay it. The point would be to unify behavior between backspace to the
-        // end of a word and manually put the pointer at the end of the word.
 
         // Make a note of the cursor position
         mLastSelectionStart = newSelStart;
@@ -984,7 +993,8 @@
             }
         }
         if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return;
-        mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
+        mApplicationSpecifiedCompletions =
+                CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions);
         if (applicationSpecifiedCompletions == null) {
             clearSuggestionStrip();
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -1145,11 +1155,11 @@
         if (!mWordComposer.isComposingWord()) return;
         final String typedWord = mWordComposer.getTypedWord();
         if (typedWord.length() > 0) {
-            commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
-                    separatorString);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
             }
+            commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
+                    separatorString);
         }
     }
 
@@ -1244,10 +1254,6 @@
         } else {
             wordToEdit = word;
         }
-        mPositionalInfoForUserDictPendingAddition =
-                new PositionalInfoForUserDictPendingAddition(
-                        wordToEdit, mLastSelectionEnd, getCurrentInputEditorInfo(),
-                        mLastComposedWord.mCapitalizedMode);
         mUserDictionary.addWordToUserDictionary(wordToEdit);
     }
 
@@ -1541,7 +1547,8 @@
             }
         } else {
             final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
-            if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+            if (Character.isLetter(codePointBeforeCursor)
+                    || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
                 mSpaceState = SPACE_STATE_PHANTOM;
             }
         }
@@ -1552,7 +1559,8 @@
     private static final class BatchInputUpdater implements Handler.Callback {
         private final Handler mHandler;
         private LatinIME mLatinIme;
-        private boolean mInBatchInput; // synchronized using "this".
+        private final Object mLock = new Object();
+        private boolean mInBatchInput; // synchronized using {@link #mLock}.
 
         private BatchInputUpdater() {
             final HandlerThread handlerThread = new HandlerThread(
@@ -1583,21 +1591,25 @@
         }
 
         // Run in the UI thread.
-        public synchronized void onStartBatchInput(final LatinIME latinIme) {
-            mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
-            mLatinIme = latinIme;
-            mInBatchInput = true;
+        public void onStartBatchInput(final LatinIME latinIme) {
+            synchronized (mLock) {
+                mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
+                mLatinIme = latinIme;
+                mInBatchInput = true;
+            }
         }
 
         // Run in the Handler thread.
-        private synchronized void updateBatchInput(final InputPointers batchPointers) {
-            if (!mInBatchInput) {
-                // Batch input has ended or canceled while the message was being delivered.
-                return;
+        private void updateBatchInput(final InputPointers batchPointers) {
+            synchronized (mLock) {
+                if (!mInBatchInput) {
+                    // Batch input has ended or canceled while the message was being delivered.
+                    return;
+                }
+                final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
+                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+                        suggestedWords, false /* dismissGestureFloatingPreviewText */);
             }
-            final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
-            mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                    suggestedWords, false /* dismissGestureFloatingPreviewText */);
         }
 
         // Run in the UI thread.
@@ -1610,19 +1622,23 @@
                     .sendToTarget();
         }
 
-        public synchronized void onCancelBatchInput() {
-            mInBatchInput = false;
-            mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                    SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+        public void onCancelBatchInput() {
+            synchronized (mLock) {
+                mInBatchInput = false;
+                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+                        SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+            }
         }
 
         // Run in the UI thread.
-        public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers) {
-            mInBatchInput = false;
-            final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
-            mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
-                    suggestedWords, true /* dismissGestureFloatingPreviewText */);
-            return suggestedWords;
+        public SuggestedWords onEndBatchInput(final InputPointers batchPointers) {
+            synchronized (mLock) {
+                mInBatchInput = false;
+                final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
+                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+                        suggestedWords, true /* dismissGestureFloatingPreviewText */);
+                return suggestedWords;
+            }
         }
 
         // {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
@@ -1718,6 +1734,9 @@
         // during key repeat.
         mHandler.postUpdateShiftState();
 
+        if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) {
+            resetEntireInputState(mLastSelectionStart);
+        }
         if (mWordComposer.isComposingWord()) {
             final int length = mWordComposer.size();
             if (length > 0) {
@@ -1849,6 +1868,10 @@
             promotePhantomSpace();
         }
 
+        if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) {
+            resetEntireInputState(mLastSelectionStart);
+            isComposingWord = false;
+        }
         // NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several
         // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
         // thread here.
@@ -1907,7 +1930,6 @@
     private boolean handleSeparator(final int primaryCode, final int x, final int y,
             final int spaceState) {
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.recordTimeForLogUnitSplit();
             ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
         }
         boolean didAutoCorrect = false;
@@ -2176,8 +2198,9 @@
     // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener}
     // interface
     @Override
-    public void pickSuggestionManually(final int index, final String suggestion) {
+    public void pickSuggestionManually(final int index, final SuggestedWordInfo suggestionInfo) {
         final SuggestedWords suggestedWords = mSuggestedWords;
+        final String suggestion = suggestionInfo.mWord;
         // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
         if (suggestion.length() == 1 && isShowingPunctuationList()) {
             // Word separators are suggested before the user inputs something.
@@ -2243,7 +2266,8 @@
         // AND it's in none of our current dictionaries (main, user or otherwise).
         // Please note that if mSuggest is null, it means that everything is off: suggestion
         // and correction, so we shouldn't try to show the hint
-        final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null
+        final boolean showingAddToDictionaryHint =
+                SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind && mSuggest != null
                 // If the suggestion is not in the dictionary, the hint should be shown.
                 && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true);
 
@@ -2321,6 +2345,48 @@
     }
 
     /**
+     * Check if the cursor is touching a word. If so, restart suggestions on this word, else
+     * do nothing.
+     */
+    private void restartSuggestionsOnWordTouchedByCursor() {
+        // If the cursor is not touching a word, or if there is a selection, return right away.
+        if (mLastSelectionStart != mLastSelectionEnd) return;
+        if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
+        final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
+                0 /* additionalPrecedingWordsCount */);
+        final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+        if (range.mWord instanceof SpannableString) {
+            final SpannableString spannableString = (SpannableString)range.mWord;
+            final String typedWord = spannableString.toString();
+            int i = 0;
+            for (Object object : spannableString.getSpans(0, spannableString.length(),
+                    SuggestionSpan.class)) {
+                SuggestionSpan span = (SuggestionSpan)object;
+                for (String s : span.getSuggestions()) {
+                    ++i;
+                    if (!TextUtils.equals(s, typedWord)) {
+                        suggestions.add(new SuggestedWordInfo(s,
+                                SuggestionStripView.MAX_SUGGESTIONS - i,
+                                SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED));
+                    }
+                }
+            }
+        }
+        mWordComposer.setComposingWord(range.mWord, mKeyboardSwitcher.getKeyboard());
+        mWordComposer.setCursorPositionWithinWord(range.mCharsBefore);
+        mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore,
+                mLastSelectionEnd + range.mCharsAfter);
+        if (suggestions.isEmpty()) {
+            suggestions.add(new SuggestedWordInfo(range.mWord.toString(), 1,
+                    SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_RESUMED));
+        }
+        showSuggestionStrip(new SuggestedWords(suggestions,
+                true /* typedWordValid */, false /* willAutoCorrect */,
+                false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
+                false /* isPrediction */), range.mWord.toString());
+    }
+
+    /**
      * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
      * word, else do nothing.
      */
@@ -2328,17 +2394,18 @@
         final CharSequence word =
                 mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
         if (null != word) {
-            restartSuggestionsOnWordBeforeCursor(word);
+            final String wordString = word.toString();
+            restartSuggestionsOnWordBeforeCursor(wordString);
             // TODO: Handle the case where the user manually moves the cursor and then backs up over
             // a separator.  In that case, the current log unit should not be uncommitted.
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                ResearchLogger.getInstance().uncommitCurrentLogUnit(word.toString(),
+                ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString,
                         true /* dumpCurrentLogUnit */);
             }
         }
     }
 
-    private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) {
+    private void restartSuggestionsOnWordBeforeCursor(final String word) {
         mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
         final int length = word.length();
         mConnection.deleteSurroundingText(length, 0);
diff --git a/native/jni/src/suggest/typing_suggest.cpp b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
similarity index 61%
copy from native/jni/src/suggest/typing_suggest.cpp
copy to java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
index 56bd5b6..e6dc6db 100644
--- a/native/jni/src/suggest/typing_suggest.cpp
+++ b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-#include "typing_suggest.h"
+package com.android.inputmethod.latin;
 
-namespace latinime {
-    SuggestInterface *(*TypingSuggest::sTypingSuggestFactoryMethod)() = 0;
+import android.content.Context;
 
-    TypingSuggest::~TypingSuggest() {
-        delete mSuggestInterface;
+/**
+ * Helper class to get the metadata URI.
+ */
+public class MetadataFileUriGetter {
+    public static String getMetadataUri(Context context) {
+        return context.getString(R.string.dictionary_pack_metadata_uri);
     }
-} // namespace latinime
+}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8a7ade4..b74ea59 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,7 +17,9 @@
 package com.android.inputmethod.latin;
 
 import android.inputmethodservice.InputMethodService;
+import android.text.SpannableString;
 import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -60,11 +62,11 @@
      * This contains the committed text immediately preceding the cursor and the composing
      * text if any. It is refreshed when the cursor moves by calling upon the TextView.
      */
-    private StringBuilder mCommittedTextBeforeComposingText = new StringBuilder();
+    private final StringBuilder mCommittedTextBeforeComposingText = new StringBuilder();
     /**
      * This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
      */
-    private StringBuilder mComposingText = new StringBuilder();
+    private final StringBuilder mComposingText = new StringBuilder();
     // A hint on how many characters to cache from the TextView. A good value of this is given by
     // how many characters we need to be able to almost always find the caps mode.
     private static final int DEFAULT_TEXT_CACHE_SIZE = 100;
@@ -334,13 +336,15 @@
         mCurrentCursorPosition = end;
         final CharSequence textBeforeCursor =
                 getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0);
-        final int indexOfStartOfComposingText =
-                Math.max(textBeforeCursor.length() - (end - start), 0);
-        mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText,
-                textBeforeCursor.length()));
         mCommittedTextBeforeComposingText.setLength(0);
-        mCommittedTextBeforeComposingText.append(
-                textBeforeCursor.subSequence(0, indexOfStartOfComposingText));
+        if (!TextUtils.isEmpty(textBeforeCursor)) {
+            final int indexOfStartOfComposingText =
+                    Math.max(textBeforeCursor.length() - (end - start), 0);
+            mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText,
+                    textBeforeCursor.length()));
+            mCommittedTextBeforeComposingText.append(
+                    textBeforeCursor.subSequence(0, indexOfStartOfComposingText));
+        }
         if (null != mIC) {
             mIC.setComposingRegion(start, end);
         }
@@ -390,7 +394,9 @@
     public void commitCompletion(final CompletionInfo completionInfo) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
-        final CharSequence text = completionInfo.getText();
+        CharSequence text = completionInfo.getText();
+        // text should never be null, but just in case, it's better to insert nothing than to crash
+        if (null == text) text = "";
         mCommittedTextBeforeComposingText.append(text);
         mCurrentCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
@@ -440,9 +446,9 @@
         public final int mCharsAfter;
 
         /** The actual characters that make up a word */
-        public final String mWord;
+        public final CharSequence mWord;
 
-        public Range(int charsBefore, int charsAfter, String word) {
+        public Range(int charsBefore, int charsAfter, CharSequence word) {
             if (charsBefore < 0 || charsAfter < 0) {
                 throw new IndexOutOfBoundsException();
             }
@@ -496,22 +502,12 @@
      *   separator. For example, if the field contains "he|llo world", where |
      *   represents the cursor, then "hello " will be returned.
      */
-    public String getWordAtCursor(String separators) {
+    public CharSequence getWordAtCursor(String separators) {
         // getWordRangeAtCursor returns null if the connection is null
         Range r = getWordRangeAtCursor(separators, 0);
         return (r == null) ? null : r.mWord;
     }
 
-    private int getCursorPosition() {
-        mIC = mParent.getCurrentInputConnection();
-        if (null == mIC) return INVALID_CURSOR_POSITION;
-        final ExtractedText extracted = mIC.getExtractedText(new ExtractedTextRequest(), 0);
-        if (extracted == null) {
-            return INVALID_CURSOR_POSITION;
-        }
-        return extracted.startOffset + extracted.selectionStart;
-    }
-
     /**
      * Returns the text surrounding the cursor.
      *
@@ -525,8 +521,10 @@
         if (mIC == null || sep == null) {
             return null;
         }
-        final CharSequence before = mIC.getTextBeforeCursor(1000, 0);
-        final CharSequence after = mIC.getTextAfterCursor(1000, 0);
+        final CharSequence before = mIC.getTextBeforeCursor(1000,
+                InputConnection.GET_TEXT_WITH_STYLES);
+        final CharSequence after = mIC.getTextAfterCursor(1000,
+                InputConnection.GET_TEXT_WITH_STYLES);
         if (before == null || after == null) {
             return null;
         }
@@ -568,8 +566,9 @@
             }
         }
 
-        final String word = before.toString().substring(startIndexInBefore, before.length())
-                + after.toString().substring(0, endIndexInAfter);
+        final SpannableString word = new SpannableString(TextUtils.concat(
+                before.subSequence(startIndexInBefore, before.length()),
+                after.subSequence(0, endIndexInAfter)));
         return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index ce659bf..318d2b2 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -134,6 +134,10 @@
         return mSettingsValues.mIsInternal;
     }
 
+    public String getWordSeparators() {
+        return mSettingsValues.mWordSeparators;
+    }
+
     // Accessed from the settings interface, hence public
     public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
             final Resources res) {
@@ -272,6 +276,11 @@
 
     public static boolean readShowSetupWizardIcon(final SharedPreferences prefs,
             final Context context) {
+        final boolean enableSetupWizardByConfig = context.getResources().getBoolean(
+                R.bool.config_setup_wizard_available);
+        if (!enableSetupWizardByConfig) {
+            return false;
+        }
         if (!prefs.contains(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
             final ApplicationInfo appInfo = context.getApplicationInfo();
             final boolean isApplicationInSystemImage =
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/SettingsActivity.java
index 99b572e..37ac2e3 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/SettingsActivity.java
@@ -25,7 +25,10 @@
     @Override
     public Intent getIntent() {
         final Intent intent = super.getIntent();
-        intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
+        final String fragment = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
+        if (fragment == null) {
+            intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
+        }
         intent.putExtra(EXTRA_NO_HEADERS, true);
         return intent;
     }
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 928141c..5405a5e 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -165,6 +165,10 @@
                     Settings.readKeyPreviewPopupEnabled(prefs, res));
         }
 
+        if (!res.getBoolean(R.bool.config_setup_wizard_available)) {
+            removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON, advancedSettings);
+        }
+
         setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST,
                 Settings.readShowsLanguageSwitchKey(prefs));
 
@@ -203,7 +207,9 @@
         final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
         final CheckBoxPreference showSetupWizardIcon =
                 (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
-        showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
+        if (showSetupWizardIcon != null) {
+            showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
+        }
         updateShowCorrectionSuggestionsSummary();
         updateKeyPreviewPopupDelaySummary();
         updateCustomInputStylesSummary();
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 90c3fcd..3ca209d 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -22,6 +22,10 @@
 import java.util.Locale;
 
 public final class StringUtils {
+    public static final int CAPITALIZE_NONE = 0;  // No caps, or mixed case
+    public static final int CAPITALIZE_FIRST = 1; // First only
+    public static final int CAPITALIZE_ALL = 2;   // All caps
+
     private StringUtils() {
         // This utility class is not publicly instantiable.
     }
@@ -102,20 +106,30 @@
         }
     }
 
-    public static String toTitleCase(final String s, final Locale locale) {
+    public static String capitalizeFirstCodePoint(final String s, final Locale locale) {
         if (s.length() <= 1) {
-            // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
-            return s;
+            return s.toUpperCase(locale);
+        }
+        // Please refer to the comment below in
+        // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings
+        final int cutoff = s.offsetByCodePoints(0, 1);
+        return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff);
+    }
+
+    public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) {
+        if (s.length() <= 1) {
+            return s.toUpperCase(locale);
         }
         // TODO: fix the bugs below
         // - This does not work for Greek, because it returns upper case instead of title case.
         // - It does not work for Serbian, because it fails to account for the "lj" character,
         // which should be "Lj" in title case and "LJ" in upper case.
-        // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
-        // are two different characters but both should be capitalized as "IJ" as if they were
-        // a single letter.
-        // - It also does not work with unicode surrogate code points.
-        return s.toUpperCase(locale).charAt(0) + s.substring(1);
+        // - It does not work for Dutch, because it fails to account for the "ij" digraph when it's
+        // written as two separate code points. They are two different characters but both should
+        // be capitalized as "IJ" as if they were a single letter in most words (not all). If the
+        // unicode char for the ligature is used however, it works.
+        final int cutoff = s.offsetByCodePoints(0, 1);
+        return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff).toLowerCase(locale);
     }
 
     private static final int[] EMPTY_CODEPOINTS = {};
@@ -171,4 +185,112 @@
         }
         return list.toArray(new String[list.size()]);
     }
+
+    // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE.
+    public static int getCapitalizationType(final String text) {
+        // If the first char is not uppercase, then the word is either all lower case or
+        // camel case, and in either case we return CAPITALIZE_NONE.
+        final int len = text.length();
+        int index = 0;
+        for (; index < len; index = text.offsetByCodePoints(index, 1)) {
+            if (Character.isLetter(text.codePointAt(index))) {
+                break;
+            }
+        }
+        if (index == len) return CAPITALIZE_NONE;
+        if (!Character.isUpperCase(text.codePointAt(index))) {
+            return CAPITALIZE_NONE;
+        }
+        int capsCount = 1;
+        int letterCount = 1;
+        for (index = text.offsetByCodePoints(index, 1); index < len;
+                index = text.offsetByCodePoints(index, 1)) {
+            if (1 != capsCount && letterCount != capsCount) break;
+            final int codePoint = text.codePointAt(index);
+            if (Character.isUpperCase(codePoint)) {
+                ++capsCount;
+                ++letterCount;
+            } else if (Character.isLetter(codePoint)) {
+                // We need to discount non-letters since they may not be upper-case, but may
+                // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME")
+                ++letterCount;
+            }
+        }
+        // We know the first char is upper case. So we want to test if either every letter other
+        // than the first is lower case, or if they are all upper case. If the string is exactly
+        // one char long, then we will arrive here with letterCount 1, and this is correct, too.
+        if (1 == capsCount) return CAPITALIZE_FIRST;
+        return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
+    }
+
+    public static boolean isIdenticalAfterUpcase(final String text) {
+        final int len = text.length();
+        for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+            final int codePoint = text.codePointAt(i);
+            if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean isIdenticalAfterDowncase(final String text) {
+        final int len = text.length();
+        for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+            final int codePoint = text.codePointAt(i);
+            if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
+            final String separators) {
+        boolean needCapsNext = true;
+        final int len = text.length();
+        for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+            final int codePoint = text.codePointAt(i);
+            if (Character.isLetter(codePoint)) {
+                if ((needCapsNext && !Character.isUpperCase(codePoint))
+                        || (!needCapsNext && !Character.isLowerCase(codePoint))) {
+                    return false;
+                }
+            }
+            // We need a capital letter next if this is a separator.
+            needCapsNext = (-1 != separators.indexOf(codePoint));
+        }
+        return true;
+    }
+
+    // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph
+    // which should be capitalized together in *some* cases.
+    public static String capitalizeEachWord(final String text, final String separators,
+            final Locale locale) {
+        final StringBuilder builder = new StringBuilder();
+        boolean needCapsNext = true;
+        final int len = text.length();
+        for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+            final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1));
+            if (needCapsNext) {
+                builder.append(nextChar.toUpperCase(locale));
+            } else {
+                builder.append(nextChar.toLowerCase(locale));
+            }
+            // We need a capital letter next if this is a separator.
+            needCapsNext = (-1 != separators.indexOf(nextChar.codePointAt(0)));
+        }
+        return builder.toString();
+    }
+
+    public static boolean containsAny(final String string, final String separators) {
+        final int len = separators.length();
+        for (int i = 0; i < len; i = separators.offsetByCodePoints(i, 1)) {
+            final int separator = separators.codePointAt(i);
+            if (-1 != string.indexOf(separator)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 5e28cc2..4d88ecc 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -183,7 +183,7 @@
             final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
             displayName = locale.getDisplayName(displayLocale);
         }
-        return StringUtils.toTitleCase(displayName, displayLocale);
+        return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
     }
 
     // InputMethodSubtype's display name in its locale.
@@ -243,7 +243,7 @@
                 }
             }
         };
-        return StringUtils.toTitleCase(
+        return StringUtils.capitalizeFirstCodePoint(
                 getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 975664d..6464bd0 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -394,7 +394,7 @@
         if (isAllUpperCase) {
             sb.append(wordInfo.mWord.toUpperCase(locale));
         } else if (isFirstCharCapitalized) {
-            sb.append(StringUtils.toTitleCase(wordInfo.mWord, locale));
+            sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
         } else {
             sb.append(wordInfo.mWord);
         }
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 3d6fe2d..158cc11 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -131,6 +131,7 @@
         public static final int KIND_APP_DEFINED = 6; // Suggested by the application
         public static final int KIND_SHORTCUT = 7; // A shortcut
         public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
+        public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span)
         public final String mWord;
         public final int mScore;
         public final int mKind; // one of the KIND_* constants above
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 0d5bde6..90f9297 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -20,7 +20,6 @@
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
-import android.content.Intent;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
@@ -28,7 +27,10 @@
 import android.provider.UserDictionary.Words;
 import android.text.TextUtils;
 
+import com.android.inputmethod.compat.UserDictionaryCompatUtils;
+
 import java.util.Arrays;
+import java.util.Locale;
 
 /**
  * An expandable dictionary that stores the words in the user dictionary provider into a binary
@@ -61,10 +63,6 @@
 
     private static final String NAME = "userunigram";
 
-    // This is not exported by the framework so we pretty much have to write it here verbatim
-    private static final String ACTION_USER_DICTIONARY_INSERT =
-            "com.android.settings.USER_DICTIONARY_INSERT";
-
     private ContentObserver mObserver;
     final private String mLocale;
     final private boolean mAlsoUseMoreRestrictiveLocales;
@@ -211,23 +209,19 @@
     /**
      * Adds a word to the user dictionary and makes it persistent.
      *
-     * This will call upon the system interface to do the actual work through the intent readied by
-     * the system to this effect.
-     *
      * @param word the word to add. If the word is capitalized, then the dictionary will
      * recognize it as a capitalized word when searched.
      */
     public synchronized void addWordToUserDictionary(final String word) {
-        // TODO: do something for the UI. With the following, any sufficiently long word will
-        // look like it will go to the user dictionary but it won't.
-        // Safeguard against adding long words. Can cause stack overflow.
-        if (word.length() >= MAX_WORD_LENGTH) return;
-
-        Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
-        intent.putExtra(Words.WORD, word);
-        intent.putExtra(Words.LOCALE, mLocale);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
+        // Update the user dictionary provider
+        final Locale locale;
+        if (USER_DICTIONARY_ALL_LANGUAGES == mLocale) {
+            locale = null;
+        } else {
+            locale = LocaleUtils.constructLocaleFromString(mLocale);
+        }
+        UserDictionaryCompatUtils.addWord(mContext, word,
+                HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY, null, locale);
     }
 
     private int scaleFrequencyFromDefaultToLatinIme(final int defaultFrequency) {
@@ -258,10 +252,10 @@
                 final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency);
                 // Safeguard against adding really long words.
                 if (word.length() < MAX_WORD_LENGTH) {
-                    super.addWord(word, null, adjustedFrequency);
+                    super.addWord(word, null, adjustedFrequency, false /* isNotAWord */);
                 }
                 if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
-                    super.addWord(shortcut, word, adjustedFrequency);
+                    super.addWord(shortcut, word, adjustedFrequency, true /* isNotAWord */);
                 }
                 cursor.moveToNext();
             }
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index 62f2a97..1093155 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -207,7 +207,12 @@
             final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey());
             if (attrList != null) {
                 for (final PendingAttribute attr : attrList) {
-                    to.setBigram(word1, unigrams.get(attr.mAddress),
+                    final String word2 = unigrams.get(attr.mAddress);
+                    if (word1 == null || word2 == null) {
+                        Log.e(TAG, "Invalid bigram pair detected: " + word1 + ", " + word2);
+                        continue;
+                    }
+                    to.setBigram(word1, word2,
                             BinaryDictInputOutput.reconstructBigramFrequency(unigramFrequency,
                                     attr.mFrequency));
                 }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index f7cb434..1af1242 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -49,6 +49,7 @@
     private int mCapitalizedMode;
     private int mTrailingSingleQuotesCount;
     private int mCodePointSize;
+    private int mCursorPositionWithinWord;
 
     /**
      * Whether the user chose to capitalize the first char of the word.
@@ -62,6 +63,7 @@
         mTrailingSingleQuotesCount = 0;
         mIsResumed = false;
         mIsBatchMode = false;
+        mCursorPositionWithinWord = 0;
         refreshSize();
     }
 
@@ -76,6 +78,7 @@
         mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
         mIsResumed = source.mIsResumed;
         mIsBatchMode = source.mIsBatchMode;
+        mCursorPositionWithinWord = source.mCursorPositionWithinWord;
         refreshSize();
     }
 
@@ -91,6 +94,7 @@
         mTrailingSingleQuotesCount = 0;
         mIsResumed = false;
         mIsBatchMode = false;
+        mCursorPositionWithinWord = 0;
         refreshSize();
     }
 
@@ -135,6 +139,7 @@
         final int newIndex = size();
         mTypedWord.appendCodePoint(primaryCode);
         refreshSize();
+        mCursorPositionWithinWord = mCodePointSize;
         if (newIndex < MAX_WORD_LENGTH) {
             mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
                     ? Character.toLowerCase(primaryCode) : primaryCode;
@@ -158,6 +163,14 @@
         mAutoCorrection = null;
     }
 
+    public void setCursorPositionWithinWord(final int posWithinWord) {
+        mCursorPositionWithinWord = posWithinWord;
+    }
+
+    public boolean isCursorAtEndOfComposingWord() {
+        return mCursorPositionWithinWord == mCodePointSize;
+    }
+
     public void setBatchInputPointers(final InputPointers batchPointers) {
         mInputPointers.set(batchPointers);
         mIsBatchMode = true;
@@ -242,6 +255,7 @@
                 ++mTrailingSingleQuotesCount;
             }
         }
+        mCursorPositionWithinWord = mCodePointSize;
         mAutoCorrection = null;
     }
 
@@ -368,6 +382,7 @@
         mCapitalizedMode = CAPS_MODE_OFF;
         refreshSize();
         mAutoCorrection = null;
+        mCursorPositionWithinWord = 0;
         mIsResumed = false;
         return lastComposedWord;
     }
@@ -380,6 +395,7 @@
         refreshSize();
         mCapitalizedMode = lastComposedWord.mCapitalizedMode;
         mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
+        mCursorPositionWithinWord = mCodePointSize;
         mIsResumed = true;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index 699e47b..dc937fb 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -28,5 +28,5 @@
     // USES_DEVELOPMENT_ONLY_DIAGNOSTICS must be false for any production build.
     public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false;
 
-    public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = true;
+    public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false;
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 5c80559..17d2815 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -620,34 +620,34 @@
      * Helper method to find a word in a given branch.
      */
     @SuppressWarnings("unused")
-    public static CharGroup findWordInTree(Node node, final String s) {
+    public static CharGroup findWordInTree(Node node, final String string) {
         int index = 0;
         final StringBuilder checker = DBG ? new StringBuilder() : null;
+        final int[] codePoints = getCodePoints(string);
 
         CharGroup currentGroup;
-        final int codePointCountInS = s.codePointCount(0, s.length());
         do {
-            int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
+            int indexOfGroup = findIndexOfChar(node, codePoints[index]);
             if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
             currentGroup = node.mData.get(indexOfGroup);
 
-            if (s.length() - index < currentGroup.mChars.length) return null;
+            if (codePoints.length - index < currentGroup.mChars.length) return null;
             int newIndex = index;
-            while (newIndex < s.length() && newIndex - index < currentGroup.mChars.length) {
-                if (currentGroup.mChars[newIndex - index] != s.codePointAt(newIndex)) return null;
+            while (newIndex < codePoints.length && newIndex - index < currentGroup.mChars.length) {
+                if (currentGroup.mChars[newIndex - index] != codePoints[newIndex]) return null;
                 newIndex++;
             }
             index = newIndex;
 
             if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
-            if (index < codePointCountInS) {
+            if (index < codePoints.length) {
                 node = currentGroup.mChildren;
             }
-        } while (null != node && index < codePointCountInS);
+        } while (null != node && index < codePoints.length);
 
-        if (index < codePointCountInS) return null;
+        if (index < codePoints.length) return null;
         if (!currentGroup.isTerminal()) return null;
-        if (DBG && !s.equals(checker.toString())) return null;
+        if (DBG && !string.equals(checker.toString())) return null;
         return currentGroup;
     }
 
@@ -847,26 +847,29 @@
         @Override
         public Word next() {
             Position currentPos = mPositions.getLast();
-            mCurrentString.setLength(mCurrentString.length() - currentPos.length);
+            mCurrentString.setLength(currentPos.length);
 
             do {
                 if (currentPos.pos.hasNext()) {
                     final CharGroup currentGroup = currentPos.pos.next();
-                    currentPos.length = currentGroup.mChars.length;
-                    for (int i : currentGroup.mChars)
+                    currentPos.length = mCurrentString.length();
+                    for (int i : currentGroup.mChars) {
                         mCurrentString.append(Character.toChars(i));
+                    }
                     if (null != currentGroup.mChildren) {
                         currentPos = new Position(currentGroup.mChildren.mData);
+                        currentPos.length = mCurrentString.length();
                         mPositions.addLast(currentPos);
                     }
-                    if (currentGroup.mFrequency >= 0)
+                    if (currentGroup.mFrequency >= 0) {
                         return new Word(mCurrentString.toString(), currentGroup.mFrequency,
                                 currentGroup.mShortcutTargets, currentGroup.mBigrams,
                                 currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry);
+                    }
                 } else {
                     mPositions.removeLast();
                     currentPos = mPositions.getLast();
-                    mCurrentString.setLength(mCurrentString.length() - mPositions.getLast().length);
+                    mCurrentString.setLength(mPositions.getLast().length);
                 }
             } while (true);
         }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 38a2648..2d0a89b 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -58,10 +58,6 @@
 
     public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
 
-    public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
-    public static final int CAPITALIZE_FIRST = 1; // First only
-    public static final int CAPITALIZE_ALL = 2; // All caps
-
     private final static String[] EMPTY_STRING_ARRAY = new String[0];
     private Map<String, DictionaryPool> mDictionaryPools = CollectionUtils.newSynchronizedTreeMap();
     private Map<String, UserBinaryDictionary> mUserDictionaries =
@@ -325,16 +321,16 @@
                 }
                 Collections.reverse(mSuggestions);
                 StringUtils.removeDupes(mSuggestions);
-                if (CAPITALIZE_ALL == capitalizeType) {
+                if (StringUtils.CAPITALIZE_ALL == capitalizeType) {
                     for (int i = 0; i < mSuggestions.size(); ++i) {
                         // get(i) returns a CharSequence which is actually a String so .toString()
                         // should return the same object.
                         mSuggestions.set(i, mSuggestions.get(i).toString().toUpperCase(locale));
                     }
-                } else if (CAPITALIZE_FIRST == capitalizeType) {
+                } else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) {
                     for (int i = 0; i < mSuggestions.size(); ++i) {
                         // Likewise
-                        mSuggestions.set(i, StringUtils.toTitleCase(
+                        mSuggestions.set(i, StringUtils.capitalizeFirstCodePoint(
                                 mSuggestions.get(i).toString(), locale));
                     }
                 }
@@ -407,11 +403,7 @@
 
     public DictAndProximity createDictAndProximity(final Locale locale) {
         final int script = getScriptFromLocale(locale);
-        final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(
-                SpellCheckerProximityInfo.getProximityForScript(script),
-                SpellCheckerProximityInfo.ROW_SIZE,
-                SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH,
-                SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT);
+        final ProximityInfo proximityInfo = new SpellCheckerProximityInfo(script);
         final DictionaryCollection dictionaryCollection =
                 DictionaryFactory.createMainDictionaryFromManager(this, locale,
                         true /* useFullEditDistance */);
@@ -438,31 +430,4 @@
         }
         return new DictAndProximity(dictionaryCollection, proximityInfo);
     }
-
-    // This method assumes the text is not empty or null.
-    public static int getCapitalizationType(String text) {
-        // If the first char is not uppercase, then the word is either all lower case,
-        // and in either case we return CAPITALIZE_NONE.
-        if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE;
-        final int len = text.length();
-        int capsCount = 1;
-        int letterCount = 1;
-        for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) {
-            if (1 != capsCount && letterCount != capsCount) break;
-            final int codePoint = text.codePointAt(i);
-            if (Character.isUpperCase(codePoint)) {
-                ++capsCount;
-                ++letterCount;
-            } else if (Character.isLetter(codePoint)) {
-                // We need to discount non-letters since they may not be upper-case, but may
-                // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME")
-                ++letterCount;
-            }
-        }
-        // We know the first char is upper case. So we want to test if either every letter other
-        // than the first is lower case, or if they are all upper case. If the string is exactly
-        // one char long, then we will arrive here with letterCount 1, and this is correct, too.
-        if (1 == capsCount) return CAPITALIZE_FIRST;
-        return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 4f86a31..96b2c81 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -150,7 +150,7 @@
             // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the
             // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters.
             // Our dictionary also contains a few words with 0xF2; it would be best to check
-            // if that's correct, but a Google search does return results for these words so
+            // if that's correct, but a web search does return results for these words so
             // they are probably okay.
             return (codePoint >= 0x370 && codePoint <= 0x3FF)
                     || (codePoint >= 0x1F00 && codePoint <= 0x1FFF)
@@ -214,19 +214,19 @@
         // If the word is in there as is, then it's in the dictionary. If not, we'll test lower
         // case versions, but only if the word is not already all-lower case or mixed case.
         if (dict.isValidWord(text)) return true;
-        if (AndroidSpellCheckerService.CAPITALIZE_NONE == capitalizeType) return false;
+        if (StringUtils.CAPITALIZE_NONE == capitalizeType) return false;
 
         // If we come here, we have a capitalized word (either First- or All-).
         // Downcase the word and look it up again. If the word is only capitalized, we
         // tested all possibilities, so if it's still negative we can return false.
         final String lowerCaseText = text.toLowerCase(mLocale);
         if (dict.isValidWord(lowerCaseText)) return true;
-        if (AndroidSpellCheckerService.CAPITALIZE_FIRST == capitalizeType) return false;
+        if (StringUtils.CAPITALIZE_FIRST == capitalizeType) return false;
 
         // If the lower case version is not in the dictionary, it's still possible
         // that we have an all-caps version of a word that needs to be capitalized
         // according to the dictionary. E.g. "GERMANS" only exists in the dictionary as "Germans".
-        return dict.isValidWord(StringUtils.toTitleCase(lowerCaseText, mLocale));
+        return dict.isValidWord(StringUtils.capitalizeFirstAndDowncaseRest(lowerCaseText, mLocale));
     }
 
     // Note : this must be reentrant
@@ -296,7 +296,7 @@
                 }
             }
 
-            final int capitalizeType = AndroidSpellCheckerService.getCapitalizationType(text);
+            final int capitalizeType = StringUtils.getCapitalizationType(text);
             boolean isInDict = true;
             DictAndProximity dictInfo = null;
             try {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 49dca21..0c480ea 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -16,53 +16,40 @@
 
 package com.android.inputmethod.latin.spellcheck;
 
-import com.android.inputmethod.annotations.UsedForTesting;
+import android.util.SparseIntArray;
+
 import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 
-import java.util.TreeMap;
+public final class SpellCheckerProximityInfo extends ProximityInfo {
+    public SpellCheckerProximityInfo(final int script) {
+        super(getProximityForScript(script), PROXIMITY_GRID_WIDTH, PROXIMITY_GRID_HEIGHT);
+    }
 
-public final class SpellCheckerProximityInfo {
-    @UsedForTesting
-    final public static int NUL = Constants.NOT_A_CODE;
+    private static final int NUL = Constants.NOT_A_CODE;
 
     // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
     // native code - this value is passed at creation of the binary object and reused
     // as the size of the passed array afterwards so they can't be different.
-    final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
+    private static final int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
 
     // The number of keys in a row of the grid used by the spell checker.
-    final public static int PROXIMITY_GRID_WIDTH = 11;
+    private static final int PROXIMITY_GRID_WIDTH = 11;
     // The number of rows in the grid used by the spell checker.
-    final public static int PROXIMITY_GRID_HEIGHT = 3;
+    private static final int PROXIMITY_GRID_HEIGHT = 3;
 
-    final private static int NOT_AN_INDEX = -1;
-    final public static int NOT_A_COORDINATE_PAIR = -1;
+    private static final int NOT_AN_INDEX = -1;
+    public static final int NOT_A_COORDINATE_PAIR = -1;
 
     // Helper methods
-    final protected static void buildProximityIndices(final int[] proximity,
-            final TreeMap<Integer, Integer> indices) {
-        for (int i = 0; i < proximity.length; i += ROW_SIZE) {
-            if (NUL != proximity[i]) indices.put(proximity[i], i / ROW_SIZE);
+    static void buildProximityIndices(final int[] proximity, final int rowSize,
+            final SparseIntArray indices) {
+        for (int i = 0; i < proximity.length; i += rowSize) {
+            if (NUL != proximity[i]) indices.put(proximity[i], i / rowSize);
         }
     }
-    final protected static int computeIndex(final int characterCode,
-            final TreeMap<Integer, Integer> indices) {
-        final Integer result = indices.get(characterCode);
-        if (null == result) return NOT_AN_INDEX;
-        return result;
-    }
 
     private static final class Latin {
-        // This is a map from the code point to the index in the PROXIMITY array.
-        // At the time the native code to read the binary dictionary needs the proximity info be
-        // passed as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input
-        // character.
-        // Since we need to build such an array, we want to be able to search in our big proximity
-        // data quickly by character, and a map is probably the best way to do this.
-        final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
-
         // The proximity here is the union of
         // - the proximity for a QWERTY keyboard.
         // - the proximity for an AZERTY keyboard.
@@ -79,7 +66,7 @@
              a s d f g h j k l
                z x c v b n m
         */
-        final static int[] PROXIMITY = {
+        static final int[] PROXIMITY = {
             // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
             // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
             // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -121,16 +108,21 @@
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
         };
+
+        // This is a mapping array from the code point to the index in the PROXIMITY array.
+        // When we check the spelling of a word, we need to pass (x,y) coordinates to the native
+        // code for each letter of the word. These are most easily computed from the index in the
+        // PROXIMITY array. Since we'll need to do that very often, the index lookup from the code
+        // point needs to be as fast as possible, and a map is probably the best way to do this.
+        // To avoid unnecessary boxing conversion to Integer, here we use SparseIntArray.
+        static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
         static {
-            buildProximityIndices(PROXIMITY, INDICES);
-        }
-        static int getIndexOf(int characterCode) {
-            return computeIndex(characterCode, INDICES);
+            buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
         }
     }
 
     private static final class Cyrillic {
-        final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
         // TODO: The following table is solely based on the keyboard layout. Consult with Russian
         // speakers on commonly misspelled words/letters.
         /*
@@ -207,7 +199,7 @@
         private static final int CY_SOFT_SIGN = '\u044C'; // ь
         private static final int CY_BE = '\u0431'; // б
         private static final int CY_YU = '\u044E'; // ю
-        final static int[] PROXIMITY = {
+        static final int[] PROXIMITY = {
             // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
             // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
             // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -280,16 +272,15 @@
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
         };
+
+        static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
         static {
-            buildProximityIndices(PROXIMITY, INDICES);
-        }
-        static int getIndexOf(int characterCode) {
-            return computeIndex(characterCode, INDICES);
+            buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
         }
     }
 
     private static final class Greek {
-        final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
         // TODO: The following table is solely based on the keyboard layout. Consult with Greek
         // speakers on commonly misspelled words/letters.
         /*
@@ -354,7 +345,7 @@
         private static final int GR_BETA = '\u03B2'; // β
         private static final int GR_NU = '\u03BD'; // ν
         private static final int GR_MU = '\u03BC'; // μ
-        final static int[] PROXIMITY = {
+        static final int[] PROXIMITY = {
             // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
             // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
             // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -419,37 +410,37 @@
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
             NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
         };
+
+        static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
         static {
-            buildProximityIndices(PROXIMITY, INDICES);
-        }
-        static int getIndexOf(int characterCode) {
-            return computeIndex(characterCode, INDICES);
+            buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
         }
     }
 
-    public static int[] getProximityForScript(final int script) {
+    private static int[] getProximityForScript(final int script) {
         switch (script) {
-            case AndroidSpellCheckerService.SCRIPT_LATIN:
-                return Latin.PROXIMITY;
-            case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
-                return Cyrillic.PROXIMITY;
-            case AndroidSpellCheckerService.SCRIPT_GREEK:
-                return Greek.PROXIMITY;
-            default:
-                throw new RuntimeException("Wrong script supplied: " + script);
+        case AndroidSpellCheckerService.SCRIPT_LATIN:
+            return Latin.PROXIMITY;
+        case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+            return Cyrillic.PROXIMITY;
+        case AndroidSpellCheckerService.SCRIPT_GREEK:
+            return Greek.PROXIMITY;
+        default:
+            throw new RuntimeException("Wrong script supplied: " + script);
         }
     }
 
     private static int getIndexOfCodeForScript(final int codePoint, final int script) {
         switch (script) {
-            case AndroidSpellCheckerService.SCRIPT_LATIN:
-                return Latin.getIndexOf(codePoint);
-            case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
-                return Cyrillic.getIndexOf(codePoint);
-            case AndroidSpellCheckerService.SCRIPT_GREEK:
-                return Greek.getIndexOf(codePoint);
-            default:
-                throw new RuntimeException("Wrong script supplied: " + script);
+        case AndroidSpellCheckerService.SCRIPT_LATIN:
+            return Latin.INDICES.get(codePoint, NOT_AN_INDEX);
+        case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+            return Cyrillic.INDICES.get(codePoint, NOT_AN_INDEX);
+        case AndroidSpellCheckerService.SCRIPT_GREEK:
+            return Greek.INDICES.get(codePoint, NOT_AN_INDEX);
+        default:
+            throw new RuntimeException("Wrong script supplied: " + script);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index ed408bb..3037669 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -16,12 +16,14 @@
 
 package com.android.inputmethod.latin.suggestions;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.TypefaceUtils;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -50,16 +52,12 @@
             super();
         }
 
-        // TODO: Remove {@link MoreSuggestionsView} argument.
         public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth,
-                final int minWidth, final int maxRow, final MoreSuggestionsView view) {
+                final int minWidth, final int maxRow, final Paint paint, final Resources res) {
             clearKeys();
-            final Resources res = view.getResources();
             mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
             mDividerWidth = mDivider.getIntrinsicWidth();
-            final int padding = (int) res.getDimension(
-                    R.dimen.more_suggestions_key_horizontal_padding);
-            final Paint paint = view.newDefaultLabelPaint();
+            final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
 
             int row = 0;
             int pos = fromPos, rowStartPos = fromPos;
@@ -67,7 +65,7 @@
             while (pos < size) {
                 final String word = suggestions.getWord(pos);
                 // TODO: Should take care of text x-scaling.
-                mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
+                mWidths[pos] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
                 final int numColumn = pos - rowStartPos + 1;
                 final int columnWidth =
                         (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
@@ -169,8 +167,8 @@
         private int mFromPos;
         private int mToPos;
 
-        public Builder(final MoreSuggestionsView paneView) {
-            super(paneView.getContext(), new MoreSuggestionsParam());
+        public Builder(final Context context, final MoreSuggestionsView paneView) {
+            super(context, new MoreSuggestionsParam());
             mPaneView = paneView;
         }
 
@@ -183,7 +181,7 @@
 
             mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
             final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
-                    mPaneView);
+                    mPaneView.newLabelPaint(null /* key */), mResources);
             mFromPos = fromPos;
             mToPos = fromPos + count;
             mSuggestions = suggestions;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 438820d..94715cd 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -43,7 +43,7 @@
     }
 
     public void updateKeyboardGeometry(final int keyHeight) {
-        mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+        updateKeyDrawParams(keyHeight);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 8c3d3b0..4ef36fa 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -62,6 +62,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.ResourceUtils;
 import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.ResearchLogger;
@@ -72,7 +73,7 @@
         OnLongClickListener {
     public interface Listener {
         public void addWordToUserDictionary(String word);
-        public void pickSuggestionManually(int index, String word);
+        public void pickSuggestionManually(int index, SuggestedWordInfo word);
     }
 
     // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
@@ -595,7 +596,7 @@
         mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
         mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
                 .findViewById(R.id.more_suggestions_view);
-        mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView);
+        mMoreSuggestionsBuilder = new MoreSuggestions.Builder(context, mMoreSuggestionsView);
 
         final Resources res = context.getResources();
         mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
@@ -656,8 +657,8 @@
         @Override
         public boolean onCustomRequest(final int requestCode) {
             final int index = requestCode;
-            final String word = mSuggestedWords.getWord(index);
-            mListener.pickSuggestionManually(index, word);
+            final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
+            mListener.pickSuggestionManually(index, wordInfo);
             dismissMoreSuggestions();
             return true;
         }
@@ -807,8 +808,8 @@
         if (index >= mSuggestedWords.size())
             return;
 
-        final String word = mSuggestedWords.getWord(index);
-        mListener.pickSuggestionManually(index, word);
+        final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
+        mListener.pickSuggestionManually(index, wordInfo);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
index c5f0959..4f86526 100644
--- a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
@@ -25,9 +25,10 @@
  */
 public final class BootBroadcastReceiver extends BroadcastReceiver {
     @Override
-    public void onReceive(Context context, Intent intent) {
+    public void onReceive(final Context context, final Intent intent) {
         if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
-            ResearchLogger.scheduleUploadingService(context);
+            UploaderService.cancelAndRescheduleUploadingService(context,
+                    true /* needsRescheduling */);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java
index e1cc2da..fbfd9b5 100644
--- a/java/src/com/android/inputmethod/research/MotionEventReader.java
+++ b/java/src/com/android/inputmethod/research/MotionEventReader.java
@@ -22,6 +22,7 @@
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
 import java.io.BufferedReader;
@@ -64,6 +65,7 @@
         return replayData;
     }
 
+    @UsedForTesting
     static class ReplayData {
         final ArrayList<Integer> mActions = new ArrayList<Integer>();
         final ArrayList<PointerProperties[]> mPointerPropertiesArrays
@@ -134,6 +136,7 @@
      * },
      * </pre>
      */
+    @UsedForTesting
     /* package for test */ void readLogStatement(final JsonReader jsonReader,
             final ReplayData replayData) throws IOException {
         String logStatementType = null;
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index aa3465e..7a23ddb 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -20,16 +20,13 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.app.AlarmManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
-import android.app.PendingIntent;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
@@ -74,22 +71,17 @@
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.research.MotionEventReader.ReplayData;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStreamReader;
 import java.nio.MappedByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.charset.Charset;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.List;
-import java.util.Locale;
 import java.util.Random;
-import java.util.UUID;
+import java.util.regex.Pattern;
 
 /**
  * Logs the use of the LatinIME keyboard.
@@ -254,7 +246,8 @@
         mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
         mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            scheduleUploadingService(mLatinIME);
+            UploaderService.cancelAndRescheduleUploadingService(mLatinIME,
+                    true /* needsRescheduling */);
         }
         mReplayer.setKeyboardSwitcher(keyboardSwitcher);
     }
@@ -268,25 +261,6 @@
         ResearchSettings.writeResearchLastDirCleanupTime(mPrefs, now);
     }
 
-    /**
-     * Arrange for the UploaderService to be run on a regular basis.
-     *
-     * Any existing scheduled invocation of UploaderService is removed and rescheduled.  This may
-     * cause problems if this method is called often and frequent updates are required, but since
-     * the user will likely be sleeping at some point, if the interval is less that the expected
-     * sleep duration and this method is not called during that time, the service should be invoked
-     * at some point.
-     */
-    public static void scheduleUploadingService(Context context) {
-        final Intent intent = new Intent(context, UploaderService.class);
-        final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
-        final AlarmManager manager =
-                (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        manager.cancel(pendingIntent);
-        manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent);
-    }
-
     public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) {
         mMainKeyboardView = mainKeyboardView;
         maybeShowSplashScreen();
@@ -790,8 +764,7 @@
     }
 
     private boolean isAllowedToLog() {
-        return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog
-                && !isReplaying();
+        return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog;
     }
 
     public void requestIndicatorRedraw() {
@@ -1093,7 +1066,7 @@
             new LogStatement("LatinImeOnStartInputViewInternal", false, false, "uuid",
                     "packageName", "inputType", "imeOptions", "fieldId", "display", "model",
                     "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything",
-                    "isUsingDevelopmentOnlyDiagnosticsDebug");
+                    "isDevTeamBuild");
     public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
             final SharedPreferences prefs) {
         final ResearchLogger researchLogger = getInstance();
@@ -1115,15 +1088,30 @@
                         Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
                         Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
                         OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING,
-                        ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG);
-            } catch (NameNotFoundException e) {
-                e.printStackTrace();
+                        researchLogger.isDevTeamBuild());
+                // Commit the logUnit so the LatinImeOnStartInputViewInternal event is in its own
+                // logUnit at the beginning of the log.
+                researchLogger.commitCurrentLogUnit();
+            } catch (final NameNotFoundException e) {
+                Log.e(TAG, "NameNotFound", e);
             }
         }
     }
 
-    public void latinIME_onFinishInputViewInternal() {
-        stop();
+    // TODO: Update this heuristic pattern to something more reliable.  Developer builds tend to
+    // have the developer name and year embedded.
+    private static final Pattern developerBuildRegex = Pattern.compile("[A-Za-z]\\.20[1-9]");
+    private boolean isDevTeamBuild() {
+        try {
+            final PackageInfo packageInfo;
+            packageInfo = mLatinIME.getPackageManager().getPackageInfo(mLatinIME.getPackageName(),
+                    0);
+            final String versionName = packageInfo.versionName;
+            return !(developerBuildRegex.matcher(versionName).find());
+        } catch (final NameNotFoundException e) {
+            Log.e(TAG, "Could not determine package name", e);
+            return false;
+        }
     }
 
     /**
@@ -1208,16 +1196,22 @@
     }
 
     /**
-     * Log a call to LatinIME.onWindowHidden().
+     * The IME is finishing; it is either being destroyed, or is about to be hidden.
      *
      * UserAction: The user has performed an action that has caused the IME to be closed.  They may
      * have focused on something other than a text field, or explicitly closed it.
      */
-    private static final LogStatement LOGSTATEMENT_LATINIME_ONWINDOWHIDDEN =
-            new LogStatement("LatinIMEOnWindowHidden", false, false, "isTextTruncated", "text");
-    public static void latinIME_onWindowHidden(final int savedSelectionStart,
-            final int savedSelectionEnd, final InputConnection ic) {
-        if (ic != null) {
+    private static final LogStatement LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL =
+            new LogStatement("LatinIMEOnFinishInputViewInternal", false, false, "isTextTruncated",
+                    "text");
+    public static void latinIME_onFinishInputViewInternal(final boolean finishingInput,
+            final int savedSelectionStart, final int savedSelectionEnd, final InputConnection ic) {
+        // The finishingInput flag is set in InputMethodService.  It is true if called from
+        // doFinishInput(), which can be called as part of doStartInput().  This can happen at times
+        // when the IME is not closing, such as when powering up.  The finishinInput flag is false
+        // if called from finishViews(), which is called from hideWindow() and onDestroy().  These
+        // are the situations in which we want to finish up the researchLog.
+        if (ic != null && !finishingInput) {
             final boolean isTextTruncated;
             final String text;
             if (LOG_FULL_TEXTVIEW_CONTENTS) {
@@ -1261,8 +1255,8 @@
             // Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g.
             // during a live user test), so the normal isPotentiallyPrivate and
             // isPotentiallyRevealing flags do not apply
-            researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONWINDOWHIDDEN, isTextTruncated,
-                    text);
+            researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL,
+                    isTextTruncated, text);
             researchLogger.commitCurrentLogUnit();
             getInstance().stop();
         }
@@ -1289,7 +1283,7 @@
         if (connection != null) {
             Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
             if (range != null) {
-                word = range.mWord;
+                word = range.mWord.toString();
             }
         }
         final ResearchLogger researchLogger = getInstance();
@@ -1634,8 +1628,7 @@
         final String scrubbedAutoCorrection = scrubDigitsFromString(autoCorrection);
         final ResearchLogger researchLogger = getInstance();
         researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords);
-        researchLogger.commitCurrentLogUnitAsWord(scrubbedAutoCorrection, Long.MAX_VALUE,
-                isBatchMode);
+        researchLogger.onWordFinished(scrubbedAutoCorrection, isBatchMode);
 
         // Add the autocorrection logStatement at the end of the logUnit for the committed word.
         // We have to do this after calling commitCurrentLogUnitAsWord, because it may split the
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index 6a9f5c1..6a9717b 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -18,6 +18,8 @@
 
 import android.app.AlarmManager;
 import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 
@@ -43,11 +45,17 @@
 
     @Override
     protected void onHandleIntent(final Intent intent) {
+        // We may reach this point either because the alarm fired, or because the system explicitly
+        // requested that an Upload occur.  In the latter case, we want to cancel the alarm in case
+        // it's about to fire.
+        cancelAndRescheduleUploadingService(this, false /* needsRescheduling */);
+
         final Uploader uploader = new Uploader(this);
         if (!uploader.isPossibleToUpload()) return;
         if (isUploadingUnconditionally(intent.getExtras()) || uploader.isConvenientToUpload()) {
             uploader.doUpload();
         }
+        cancelAndRescheduleUploadingService(this, true /* needsRescheduling */);
     }
 
     private boolean isUploadingUnconditionally(final Bundle bundle) {
@@ -57,4 +65,42 @@
         }
         return false;
     }
+
+    /**
+     * Arrange for the UploaderService to be run on a regular basis.
+     *
+     * Any existing scheduled invocation of UploaderService is removed and optionally rescheduled.
+     * This may cause problems if this method is called so often that no scheduled invocation is
+     * ever run.  But if the delay is short enough that it will go off when the user is sleeping,
+     * then there should be no starvation.
+     *
+     * @param context {@link Context} object
+     * @param needsRescheduling whether to schedule a future intent to be delivered to this service
+     */
+    public static void cancelAndRescheduleUploadingService(final Context context,
+            final boolean needsRescheduling) {
+        final PendingIntent pendingIntent = getPendingIntentForService(context);
+        final AlarmManager alarmManager = (AlarmManager) context.getSystemService(
+                Context.ALARM_SERVICE);
+        cancelAnyScheduledServiceAlarm(alarmManager, pendingIntent);
+        if (needsRescheduling) {
+            scheduleServiceAlarm(alarmManager, pendingIntent);
+        }
+    }
+
+    private static PendingIntent getPendingIntentForService(final Context context) {
+        final Intent intent = new Intent(context, UploaderService.class);
+        return PendingIntent.getService(context, 0, intent, 0);
+    }
+
+    private static void cancelAnyScheduledServiceAlarm(final AlarmManager alarmManager,
+            final PendingIntent pendingIntent) {
+        alarmManager.cancel(pendingIntent);
+    }
+
+    private static void scheduleServiceAlarm(final AlarmManager alarmManager,
+            final PendingIntent pendingIntent) {
+        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, UploaderService.RUN_INTERVAL,
+                pendingIntent);
+    }
 }
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 3735ec0..cbe9515 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -24,15 +24,14 @@
 include $(CLEAR_VARS)
 
 LATIN_IME_SRC_DIR := src
-LATIN_IME_SRC_FULLPATH_DIR := $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
 
-LOCAL_C_INCLUDES += $(LATIN_IME_SRC_FULLPATH_DIR) $(LATIN_IME_SRC_FULLPATH_DIR)/suggest
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
 
 LOCAL_CFLAGS += -Werror -Wall -Wextra -Weffc++ -Wformat=2 -Wcast-qual -Wcast-align \
     -Wwrite-strings -Wfloat-equal -Wpointer-arith -Winit-self -Wredundant-decls -Wno-system-headers
 
 ifeq ($(TARGET_ARCH), arm)
-ifneq ($(TARGET_GCC_VERSION), 4.7)
+ifeq ($(TARGET_GCC_VERSION), 4.6)
 LOCAL_CFLAGS += -Winline
 endif # TARGET_GCC_VERSION
 endif # TARGET_ARCH
@@ -53,14 +52,27 @@
     correction.cpp \
     dictionary.cpp \
     dic_traverse_wrapper.cpp \
+    digraph_utils.cpp \
     proximity_info.cpp \
     proximity_info_params.cpp \
     proximity_info_state.cpp \
     proximity_info_state_utils.cpp \
     unigram_dictionary.cpp \
     words_priority_queue.cpp \
-    suggest/gesture_suggest.cpp \
-    suggest/typing_suggest.cpp
+    suggest/core/suggest.cpp \
+    $(addprefix suggest/core/dicnode/, \
+        dic_node.cpp \
+        dic_node_utils.cpp \
+        dic_nodes_cache.cpp) \
+    suggest/core/policy/weighting.cpp \
+    suggest/core/session/dic_traverse_session.cpp \
+    suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp \
+    $(addprefix suggest/policyimpl/typing/, \
+        scoring_params.cpp \
+        typing_scoring.cpp \
+        typing_suggest_policy.cpp \
+        typing_traversal.cpp \
+        typing_weighting.cpp)
 
 LOCAL_SRC_FILES := \
     $(LATIN_IME_JNI_SRC_FILES) \
@@ -115,6 +127,4 @@
 #################### Clean up the tmp vars
 LATIN_IME_CORE_SRC_FILES :=
 LATIN_IME_JNI_SRC_FILES :=
-LATIN_IME_GESTURE_IMPL_SRC_FILES :=
 LATIN_IME_SRC_DIR :=
-LATIN_IME_SRC_FULLPATH_DIR :=
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index 30ca3f1..dedb02a 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -26,13 +26,13 @@
 
 static jlong latinime_Keyboard_setProximityInfo(JNIEnv *env, jclass clazz, jstring localeJStr,
         jint displayWidth, jint displayHeight, jint gridWidth, jint gridHeight,
-        jint mostCommonkeyWidth, jintArray proximityChars, jint keyCount,
+        jint mostCommonkeyWidth, jint mostCommonkeyHeight, jintArray proximityChars, jint keyCount,
         jintArray keyXCoordinates, jintArray keyYCoordinates, jintArray keyWidths,
         jintArray keyHeights, jintArray keyCharCodes, jfloatArray sweetSpotCenterXs,
         jfloatArray sweetSpotCenterYs, jfloatArray sweetSpotRadii) {
     ProximityInfo *proximityInfo = new ProximityInfo(env, localeJStr, displayWidth, displayHeight,
-            gridWidth, gridHeight, mostCommonkeyWidth, proximityChars, keyCount,
-            keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
+            gridWidth, gridHeight, mostCommonkeyWidth, mostCommonkeyHeight, proximityChars,
+            keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
             sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
     return reinterpret_cast<jlong>(proximityInfo);
 }
@@ -43,9 +43,12 @@
 }
 
 static JNINativeMethod sMethods[] = {
-    {"setProximityInfoNative", "(Ljava/lang/String;IIIII[II[I[I[I[I[I[F[F[F)J",
-            reinterpret_cast<void *>(latinime_Keyboard_setProximityInfo)},
-    {"releaseProximityInfoNative", "(J)V", reinterpret_cast<void *>(latinime_Keyboard_release)}
+    {const_cast<char *>("setProximityInfoNative"),
+     const_cast<char *>("(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J"),
+     reinterpret_cast<void *>(latinime_Keyboard_setProximityInfo)},
+    {const_cast<char *>("releaseProximityInfoNative"),
+     const_cast<char *>("(J)V"),
+     reinterpret_cast<void *>(latinime_Keyboard_release)}
 };
 
 int register_ProximityInfo(JNIEnv *env) {
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 9321c4b..11fa3da 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -280,19 +280,27 @@
 }
 
 static JNINativeMethod sMethods[] = {
-    {"openNative", "(Ljava/lang/String;JJ)J",
-            reinterpret_cast<void *>(latinime_BinaryDictionary_open)},
-    {"closeNative", "(J)V", reinterpret_cast<void *>(latinime_BinaryDictionary_close)},
-    {"getSuggestionsNative", "(JJJ[I[I[I[I[IIIZ[IZ[I[I[I[I)I",
-            reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)},
-    {"getProbabilityNative", "(J[I)I",
-            reinterpret_cast<void *>(latinime_BinaryDictionary_getProbability)},
-    {"isValidBigramNative", "(J[I[I)Z",
-            reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)},
-    {"calcNormalizedScoreNative", "([I[II)F",
-            reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)},
-    {"editDistanceNative", "([I[I)I",
-            reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)}
+    {const_cast<char *>("openNative"),
+     const_cast<char *>("(Ljava/lang/String;JJ)J"),
+     reinterpret_cast<void *>(latinime_BinaryDictionary_open)},
+    {const_cast<char *>("closeNative"),
+     const_cast<char *>("(J)V"),
+     reinterpret_cast<void *>(latinime_BinaryDictionary_close)},
+    {const_cast<char *>("getSuggestionsNative"),
+     const_cast<char *>("(JJJ[I[I[I[I[IIIZ[IZ[I[I[I[I)I"),
+     reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)},
+    {const_cast<char *>("getProbabilityNative"),
+     const_cast<char *>("(J[I)I"),
+     reinterpret_cast<void *>(latinime_BinaryDictionary_getProbability)},
+    {const_cast<char *>("isValidBigramNative"),
+     const_cast<char *>("(J[I[I)Z"),
+     reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)},
+    {const_cast<char *>("calcNormalizedScoreNative"),
+     const_cast<char *>("([I[II)F"),
+     reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)},
+    {const_cast<char *>("editDistanceNative"),
+     const_cast<char *>("([I[I)I"),
+     reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)}
 };
 
 int register_BinaryDictionary(JNIEnv *env) {
diff --git a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
index 9b39245..dfe3b09 100644
--- a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
@@ -48,12 +48,15 @@
 }
 
 static JNINativeMethod sMethods[] = {
-    {"setDicTraverseSessionNative", "(Ljava/lang/String;)J",
-            reinterpret_cast<void *>(latinime_setDicTraverseSession)},
-    {"initDicTraverseSessionNative", "(JJ[II)V",
-            reinterpret_cast<void *>(latinime_initDicTraverseSession)},
-    {"releaseDicTraverseSessionNative", "(J)V",
-            reinterpret_cast<void *>(latinime_releaseDicTraverseSession)}
+    {const_cast<char *>("setDicTraverseSessionNative"),
+     const_cast<char *>("(Ljava/lang/String;)J"),
+     reinterpret_cast<void *>(latinime_setDicTraverseSession)},
+    {const_cast<char *>("initDicTraverseSessionNative"),
+     const_cast<char *>("(JJ[II)V"),
+     reinterpret_cast<void *>(latinime_initDicTraverseSession)},
+    {const_cast<char *>("releaseDicTraverseSessionNative"),
+     const_cast<char *>("(J)V"),
+     reinterpret_cast<void *>(latinime_releaseDicTraverseSession)}
 };
 
 int register_DicTraverseSession(JNIEnv *env) {
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index 1ea2041..8e5c508 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -16,12 +16,12 @@
 
 #define LOG_TAG "LatinIME: jni"
 
+#include "jni_common.h"
+
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
 #include "com_android_inputmethod_latin_DicTraverseSession.h"
 #include "defines.h"
-#include "jni.h"
-#include "jni_common.h"
 
 /*
  * Returns the JNI version on success, -1 on failure.
diff --git a/native/jni/src/bigram_dictionary.cpp b/native/jni/src/bigram_dictionary.cpp
index 43e59a2..9289038 100644
--- a/native/jni/src/bigram_dictionary.cpp
+++ b/native/jni/src/bigram_dictionary.cpp
@@ -39,7 +39,7 @@
 void BigramDictionary::addWordBigram(int *word, int length, int probability, int *bigramProbability,
         int *bigramCodePoints, int *outputTypes) const {
     word[length] = 0;
-    if (DEBUG_DICT) {
+    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]);
@@ -57,7 +57,7 @@
         }
         insertAt++;
     }
-    if (DEBUG_DICT) {
+    if (DEBUG_DICT_FULL) {
         AKLOGI("Bigram: InsertAt -> %d MAX_RESULTS: %d", insertAt, MAX_RESULTS);
     }
     if (insertAt >= MAX_RESULTS) {
@@ -76,7 +76,7 @@
         *dest++ = *word++;
     }
     *dest = 0; // NULL terminate
-    if (DEBUG_DICT) {
+    if (DEBUG_DICT_FULL) {
         AKLOGI("Bigram: Added word at %d", insertAt);
     }
 }
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 671507e..76234f8 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -954,7 +954,13 @@
 
 
 // In dictionary.cpp, getSuggestion() method,
-// suggestion scores are computed using the below formula.
+// When USE_SUGGEST_INTERFACE_FOR_TYPING is true:
+//   SUGGEST_INTERFACE_OUTPUT_SCALE was multiplied to the original suggestion scores to convert
+//   them to integers.
+//     score = (int)((original score) * SUGGEST_INTERFACE_OUTPUT_SCALE)
+//   Undo the scaling here to recover the original score.
+//     normalizedScore = ((float)score) / SUGGEST_INTERFACE_OUTPUT_SCALE
+// Otherwise: suggestion scores are computed using the below formula.
 // original score
 //  := powf(mTypedLetterMultiplier (this is defined 2),
 //         (the number of matched characters between typed word and suggested word))
@@ -991,16 +997,20 @@
         return 0.0f;
     }
 
+    // add a weight based on edit distance.
+    // distance <= max(afterLength, beforeLength) == afterLength,
+    // so, 0 <= distance / afterLength <= 1
+    const float weight = 1.0f - static_cast<float>(distance) / static_cast<float>(afterLength);
+
+    if (USE_SUGGEST_INTERFACE_FOR_TYPING) {
+        return (static_cast<float>(score) / SUGGEST_INTERFACE_OUTPUT_SCALE) * weight;
+    }
     const float maxScore = score >= S_INT_MAX ? static_cast<float>(S_INT_MAX)
             : static_cast<float>(MAX_INITIAL_SCORE)
                     * powf(static_cast<float>(TYPED_LETTER_MULTIPLIER),
                             static_cast<float>(min(beforeLength, afterLength - spaceCount)))
                     * static_cast<float>(FULL_WORD_MULTIPLIER);
 
-    // add a weight based on edit distance.
-    // distance <= max(afterLength, beforeLength) == afterLength,
-    // so, 0 <= distance / afterLength <= 1
-    const float weight = 1.0f - static_cast<float>(distance) / static_cast<float>(afterLength);
     return (static_cast<float>(score) / maxScore) * weight;
 }
 } // namespace latinime
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index f0d6210..a9e9b48 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -307,7 +307,7 @@
     mNeedsToTraverseAllNodes = true;
 }
 
-inline bool Correction::isSingleQuote(const int c) {
+AK_FORCE_INLINE bool Correction::isSingleQuote(const int c) {
     const int userTypedChar = mProximityInfoState.getPrimaryCodePointAt(mInputIndex);
     return (c == KEYCODE_SINGLE_QUOTE && userTypedChar != KEYCODE_SINGLE_QUOTE);
 }
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index 6e09815..a7b023a 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -216,6 +216,7 @@
 #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
@@ -241,6 +242,7 @@
 #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
 
@@ -287,6 +289,7 @@
 
 #define CALIBRATE_SCORE_BY_TOUCH_COORDINATES true
 #define SUGGEST_MULTIPLE_WORDS true
+#define USE_SUGGEST_INTERFACE_FOR_TYPING true
 #define SUGGEST_INTERFACE_OUTPUT_SCALE 1000000.0f
 
 // The following "rate"s are used as a multiplier before dividing by 100, so they are in percent.
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
index 6deab36..c998c06 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/dictionary.cpp
@@ -16,14 +16,18 @@
 
 #define LOG_TAG "LatinIME: dictionary.cpp"
 
+#include "dictionary.h"
+
+#include <map> // TODO: remove
 #include <stdint.h>
 
 #include "bigram_dictionary.h"
 #include "binary_format.h"
 #include "defines.h"
-#include "dictionary.h"
 #include "dic_traverse_wrapper.h"
-#include "gesture_suggest.h"
+#include "suggest/core/suggest.h"
+#include "suggest/policyimpl/gesture/gesture_suggest_policy_factory.h"
+#include "suggest/policyimpl/typing/typing_suggest_policy_factory.h"
 #include "unigram_dictionary.h"
 
 namespace latinime {
@@ -34,13 +38,15 @@
           mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust),
           mUnigramDictionary(new UnigramDictionary(mOffsetDict, BinaryFormat::getFlags(mDict))),
           mBigramDictionary(new BigramDictionary(mOffsetDict)),
-          mGestureSuggest(new GestureSuggest()) {
+          mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
+          mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) {
 }
 
 Dictionary::~Dictionary() {
     delete mUnigramDictionary;
     delete mBigramDictionary;
     delete mGestureSuggest;
+    delete mTypingSuggest;
 }
 
 int Dictionary::getSuggestions(ProximityInfo *proximityInfo, void *traverseSession,
@@ -60,14 +66,26 @@
         }
         return result;
     } else {
-        std::map<int, int> bigramMap;
-        uint8_t bigramFilter[BIGRAM_FILTER_BYTE_SIZE];
-        mBigramDictionary->fillBigramAddressToProbabilityMapAndFilter(prevWordCodePoints,
-                prevWordLength, &bigramMap, bigramFilter);
-        result = mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates, ycoordinates,
-                inputCodePoints, inputSize, &bigramMap, bigramFilter, useFullEditDistance, outWords,
-                frequencies, outputTypes);
-        return result;
+        if (USE_SUGGEST_INTERFACE_FOR_TYPING) {
+            DicTraverseWrapper::initDicTraverseSession(
+                    traverseSession, this, prevWordCodePoints, prevWordLength);
+            result = mTypingSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+                    ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint,
+                    outWords, frequencies, spaceIndices, outputTypes);
+            if (DEBUG_DICT) {
+                DUMP_RESULT(outWords, frequencies);
+            }
+            return result;
+        } else {
+            std::map<int, int> bigramMap;
+            uint8_t bigramFilter[BIGRAM_FILTER_BYTE_SIZE];
+            mBigramDictionary->fillBigramAddressToProbabilityMapAndFilter(prevWordCodePoints,
+                    prevWordLength, &bigramMap, bigramFilter);
+            result = mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates, ycoordinates,
+                    inputCodePoints, inputSize, &bigramMap, bigramFilter, useFullEditDistance,
+                    outWords, frequencies, outputTypes);
+            return result;
+        }
     }
 }
 
@@ -85,4 +103,9 @@
 bool Dictionary::isValidBigram(const int *word1, int length1, const int *word2, int length2) const {
     return mBigramDictionary->isValidBigram(word1, length1, word2, length2);
 }
+
+int Dictionary::getDictFlags() const {
+    return mUnigramDictionary->getDictFlags();
+}
+
 } // namespace latinime
diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h
index 449b95a..0653d3c 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/dictionary.h
@@ -63,6 +63,7 @@
     int getDictSize() const { return mDictSize; }
     int getMmapFd() const { return mMmapFd; }
     int getDictBufAdjust() const { return mDictBufAdjust; }
+    int getDictFlags() const;
     virtual ~Dictionary();
 
  private:
@@ -79,6 +80,7 @@
     const UnigramDictionary *mUnigramDictionary;
     const BigramDictionary *mBigramDictionary;
     SuggestInterface *mGestureSuggest;
+    SuggestInterface *mTypingSuggest;
 };
 } // namespace latinime
 #endif // LATINIME_DICTIONARY_H
diff --git a/native/jni/src/digraph_utils.cpp b/native/jni/src/digraph_utils.cpp
new file mode 100644
index 0000000..6a1ab02
--- /dev/null
+++ b/native/jni/src/digraph_utils.cpp
@@ -0,0 +1,133 @@
+/*
+ * 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 "binary_format.h"
+#include "defines.h"
+#include "digraph_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 int dictFlags, const int compositeGlyphCodePoint) {
+    const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(dictFlags);
+    if (DigraphUtils::getDigraphForDigraphTypeAndCodePoint(digraphType, compositeGlyphCodePoint)) {
+        return true;
+    }
+    return false;
+}
+
+// Returns the digraph type associated with the given dictionary.
+/* static */ DigraphUtils::DigraphType DigraphUtils::getDigraphTypeForDictionary(
+        const int dictFlags) {
+    if (BinaryFormat::REQUIRES_GERMAN_UMLAUT_PROCESSING & dictFlags) {
+        return DIGRAPH_TYPE_GERMAN_UMLAUT;
+    }
+    if (BinaryFormat::REQUIRES_FRENCH_LIGATURES_PROCESSING & dictFlags) {
+        return DIGRAPH_TYPE_FRENCH_LIGATURES;
+    }
+    return DIGRAPH_TYPE_NONE;
+}
+
+// Retrieves the set of all digraphs associated with the given dictionary flags.
+// Returns the size of the digraph array, or 0 if none exist.
+/* static */ int DigraphUtils::getAllDigraphsForDictionaryAndReturnSize(
+        const int dictFlags, const DigraphUtils::digraph_t **const digraphs) {
+    const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(dictFlags);
+    return getAllDigraphsForDigraphTypeAndReturnSize(digraphType, digraphs);
+}
+
+// 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 digraphsSize =
+            DigraphUtils::getAllDigraphsForDictionaryAndReturnSize(digraphType, &digraphs);
+    for (int i = 0; i < digraphsSize; i++) {
+        if (digraphs[i].compositeGlyph == compositeGlyphCodePoint) {
+            return &digraphs[i];
+        }
+    }
+    return 0;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/digraph_utils.h b/native/jni/src/digraph_utils.h
new file mode 100644
index 0000000..9443522
--- /dev/null
+++ b/native/jni/src/digraph_utils.h
@@ -0,0 +1,60 @@
+/*
+ * 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
+
+namespace latinime {
+
+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 int dictFlags, const int compositeGlyphCodePoint);
+    static int getAllDigraphsForDictionaryAndReturnSize(
+            const int dictFlags, const digraph_t **const digraphs);
+    static int getDigraphCodePointForIndex(const int dictFlags, const int compositeGlyphCodePoint,
+            const DigraphCodePointIndex digraphCodePointIndex);
+    static int getDigraphCodePointForIndex(const int compositeGlyphCodePoint,
+            const DigraphCodePointIndex digraphCodePointIndex);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DigraphUtils);
+    static DigraphType getDigraphTypeForDictionary(const int dictFlags);
+    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/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 74b5e01..88d670d 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -49,13 +49,17 @@
 
 ProximityInfo::ProximityInfo(JNIEnv *env, const jstring localeJStr,
         const int keyboardWidth, const int keyboardHeight, const int gridWidth,
-        const int gridHeight, const int mostCommonKeyWidth, const jintArray proximityChars,
-        const int keyCount, const jintArray keyXCoordinates, const jintArray keyYCoordinates,
-        const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
-        const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
-        const jfloatArray sweetSpotRadii)
+        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 +
+                  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)),
@@ -129,7 +133,7 @@
 }
 
 float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloatG(
-        const int keyId, const int x, const int y) const {
+        const int keyId, const int x, const int y, const float verticalScale) const {
     const bool correctTouchPosition = hasTouchPositionCorrectionData();
     const float centerX = static_cast<float>(correctTouchPosition ? getSweetSpotCenterXAt(keyId)
             : getKeyCenterXOfKeyIdG(keyId));
@@ -138,7 +142,7 @@
     if (correctTouchPosition) {
         const float sweetSpotCenterY = static_cast<float>(getSweetSpotCenterYAt(keyId));
         const float gapY = sweetSpotCenterY - visualKeyCenterY;
-        centerY = visualKeyCenterY + gapY * ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G;
+        centerY = visualKeyCenterY + gapY * verticalScale;
     } else {
         centerY = visualKeyCenterY;
     }
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index 57a175d..deb9ae0 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -30,16 +30,17 @@
  public:
     ProximityInfo(JNIEnv *env, const jstring localeJStr,
             const int keyboardWidth, const int keyboardHeight, const int gridWidth,
-            const int gridHeight, const int mostCommonKeyWidth, const jintArray proximityChars,
-            const int keyCount, const jintArray keyXCoordinates, const jintArray keyYCoordinates,
-            const jintArray keyWidths, const jintArray keyHeights, const jintArray keyCharCodes,
-            const jfloatArray sweetSpotCenterXs, const jfloatArray sweetSpotCenterYs,
-            const jfloatArray sweetSpotRadii);
+            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;
+            const int keyId, const int x, const int y,
+            const float verticalScale) const;
     bool sameAsTyped(const unsigned short *word, int length) const;
     int getCodePointOf(const int keyIndex) const;
     bool hasSweetSpotData(const int keyIndex) const {
@@ -55,6 +56,9 @@
     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; }
@@ -98,6 +102,8 @@
     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;
diff --git a/native/jni/src/proximity_info_params.cpp b/native/jni/src/proximity_info_params.cpp
index f9a4352..2675d9e 100644
--- a/native/jni/src/proximity_info_params.cpp
+++ b/native/jni/src/proximity_info_params.cpp
@@ -20,7 +20,8 @@
 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_G = 1.1f;
+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()
diff --git a/native/jni/src/proximity_info_params.h b/native/jni/src/proximity_info_params.h
index e7aec09..4e47f73 100644
--- a/native/jni/src/proximity_info_params.h
+++ b/native/jni/src/proximity_info_params.h
@@ -25,6 +25,7 @@
  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()
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index 7fcfd5d..a10b260 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -28,16 +28,19 @@
 
 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));
-    mIsContinuationPossible = ProximityInfoStateUtils::checkAndReturnIsContinuationPossible(
-            inputSize, xCoordinates, yCoordinates, times, mSampledInputSize, &mSampledInputXs,
-            &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
+    mIsContinuousSuggestionPossible =
+            ProximityInfoStateUtils::checkAndReturnIsContinuousSuggestionPossible(
+                    inputSize, xCoordinates, yCoordinates, times, mSampledInputSize,
+                    &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
     if (DEBUG_DICT) {
-        AKLOGI("isContinuationPossible = %s", (mIsContinuationPossible ? "true" : "false"));
+        AKLOGI("isContinuousSuggestionPossible = %s",
+                (mIsContinuousSuggestionPossible ? "true" : "false"));
     }
 
     mProximityInfo = proximityInfo;
@@ -64,7 +67,7 @@
     mSampledInputSize = 0;
     mMostProbableStringProbability = 0.0f;
 
-    if (mIsContinuationPossible && mSampledInputIndice.size() > 1) {
+    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(
@@ -92,12 +95,17 @@
                 pushTouchPointStartIndex, lastSavedInputSize);
     }
 
+    // TODO: Remove the dependency of "isGeometric"
+    const float verticalSweetSpotScale = isGeometric
+            ? ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G
+            : ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE;
+
     if (xCoordinates && yCoordinates) {
         mSampledInputSize = ProximityInfoStateUtils::updateTouchPoints(mProximityInfo,
                 mMaxPointToKeyLength, mInputProximities, xCoordinates, yCoordinates, times,
-                pointerIds, inputSize, isGeometric, pointerId, pushTouchPointStartIndex,
-                &mSampledInputXs, &mSampledInputYs, &mSampledTimes, &mSampledLengthCache,
-                &mSampledInputIndice);
+                pointerIds, verticalSweetSpotScale, inputSize, isGeometric, pointerId,
+                pushTouchPointStartIndex, &mSampledInputXs, &mSampledInputYs, &mSampledTimes,
+                &mSampledLengthCache, &mSampledInputIndice);
     }
 
     if (mSampledInputSize > 0 && isGeometric) {
@@ -113,8 +121,8 @@
 
     if (mSampledInputSize > 0) {
         ProximityInfoStateUtils::initGeometricDistanceInfos(mProximityInfo, mSampledInputSize,
-                lastSavedInputSize, &mSampledInputXs, &mSampledInputYs, &mSampledNearKeySets,
-                &mSampledDistanceCache_G);
+                lastSavedInputSize, verticalSweetSpotScale, &mSampledInputXs, &mSampledInputYs,
+                &mSampledNearKeySets, &mSampledDistanceCache_G);
         if (isGeometric) {
             // updates probabilities of skipping or mapping each key for all points.
             ProximityInfoStateUtils::updateAlignPointProbabilities(
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index 224240b..9bba751 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -47,12 +47,12 @@
             : mProximityInfo(0), mMaxPointToKeyLength(0.0f), mAverageSpeed(0.0f),
               mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0),
               mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
-              mIsContinuationPossible(false), mSampledInputXs(), mSampledInputYs(), mSampledTimes(),
-              mSampledInputIndice(), mSampledLengthCache(), mBeelineSpeedPercentiles(),
-              mSampledDistanceCache_G(), mSpeedRates(), mDirections(), mCharProbabilities(),
-              mSampledNearKeySets(), mSampledSearchKeySets(), mSampledSearchKeyVectors(),
-              mTouchPositionCorrectionEnabled(false), mSampledInputSize(0),
-              mMostProbableStringProbability(0.0f) {
+              mIsContinuousSuggestionPossible(false), mSampledInputXs(), mSampledInputYs(),
+              mSampledTimes(), mSampledInputIndice(), mSampledLengthCache(),
+              mBeelineSpeedPercentiles(), mSampledDistanceCache_G(), mSpeedRates(), mDirections(),
+              mCharProbabilities(), mSampledNearKeySets(), mSampledSearchKeySets(),
+              mSampledSearchKeyVectors(), mTouchPositionCorrectionEnabled(false),
+              mSampledInputSize(0), mMostProbableStringProbability(0.0f) {
         memset(mInputProximities, 0, sizeof(mInputProximities));
         memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
         memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
@@ -143,8 +143,8 @@
         return mSampledLengthCache[index];
     }
 
-    bool isContinuationPossible() const {
-        return mIsContinuationPossible;
+    bool isContinuousSuggestionPossible() const {
+        return mIsContinuousSuggestionPossible;
     }
 
     float getPointToKeyByIdLength(const int inputIndex, const int keyId) const;
@@ -223,7 +223,7 @@
     int mCellWidth;
     int mGridHeight;
     int mGridWidth;
-    bool mIsContinuationPossible;
+    bool mIsContinuousSuggestionPossible;
 
     std::vector<int> mSampledInputXs;
     std::vector<int> mSampledInputYs;
diff --git a/native/jni/src/proximity_info_state_utils.cpp b/native/jni/src/proximity_info_state_utils.cpp
index ccb28bc..df70cff 100644
--- a/native/jni/src/proximity_info_state_utils.cpp
+++ b/native/jni/src/proximity_info_state_utils.cpp
@@ -42,8 +42,8 @@
         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,
+        const float verticalSweetSpotScale, 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) {
@@ -112,10 +112,10 @@
             }
 
             if (pushTouchPoint(proximityInfo, maxPointToKeyLength, i, c, x, y, time,
-                    isGeometric /* doSampling */, i == lastInputIndex, sumAngle,
-                    currentNearKeysDistances, prevNearKeysDistances, prevPrevNearKeysDistances,
-                    sampledInputXs, sampledInputYs, sampledInputTimes, sampledLengthCache,
-                    sampledInputIndice)) {
+                    verticalSweetSpotScale, isGeometric /* doSampling */, i == lastInputIndex,
+                    sumAngle, currentNearKeysDistances, prevNearKeysDistances,
+                    prevPrevNearKeysDistances, sampledInputXs, sampledInputYs, sampledInputTimes,
+                    sampledLengthCache, sampledInputIndice)) {
                 // Previous point information was popped.
                 NearKeysDistanceMap *tmp = prevNearKeysDistances;
                 prevNearKeysDistances = currentNearKeysDistances;
@@ -222,7 +222,8 @@
 
 /* static */ void ProximityInfoStateUtils::initGeometricDistanceInfos(
         const ProximityInfo *const proximityInfo, const int sampledInputSize,
-        const int lastSavedInputSize, const std::vector<int> *const sampledInputXs,
+        const int lastSavedInputSize, const float verticalSweetSpotScale,
+        const std::vector<int> *const sampledInputXs,
         const std::vector<int> *const sampledInputYs,
         std::vector<NearKeycodesSet> *SampledNearKeySets,
         std::vector<float> *SampledDistanceCache_G) {
@@ -236,7 +237,8 @@
             const int x = (*sampledInputXs)[i];
             const int y = (*sampledInputYs)[i];
             const float normalizedSquaredDistance =
-                    proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y);
+                    proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(
+                            k, x, y, verticalSweetSpotScale);
             (*SampledDistanceCache_G)[index] = normalizedSquaredDistance;
             if (normalizedSquaredDistance
                     < ProximityInfoParams::NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD) {
@@ -354,12 +356,14 @@
 // 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, NearKeysDistanceMap *const currentNearKeysDistances) {
+        const int y, const float verticalSweetspotScale,
+        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);
+        const float dist = proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y,
+                verticalSweetspotScale);
         if (dist < ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_DISTANCE) {
             currentNearKeysDistances->insert(std::pair<int, float>(k, dist));
         }
@@ -439,7 +443,8 @@
 // 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 doSampling, const bool isLastPoint, const float sumAngle,
+        const int time, const float verticalSweetSpotScale, const bool doSampling,
+        const bool isLastPoint, const float sumAngle,
         NearKeysDistanceMap *const currentNearKeysDistances,
         const NearKeysDistanceMap *const prevNearKeysDistances,
         const NearKeysDistanceMap *const prevPrevNearKeysDistances,
@@ -451,8 +456,8 @@
     size_t size = sampledInputXs->size();
     bool popped = false;
     if (nodeCodePoint < 0 && doSampling) {
-        const float nearest = updateNearKeysDistances(
-                proximityInfo, maxPointToKeyLength, x, y, currentNearKeysDistances);
+        const float nearest = updateNearKeysDistances(proximityInfo, maxPointToKeyLength, x, y,
+                verticalSweetSpotScale, currentNearKeysDistances);
         const float score = getPointScore(mostCommonKeyWidth, x, y, time, isLastPoint, nearest,
                 sumAngle, currentNearKeysDistances, prevNearKeysDistances,
                 prevPrevNearKeysDistances, sampledInputXs, sampledInputYs);
@@ -968,10 +973,10 @@
     return true;
 }
 
-/* static */ bool ProximityInfoStateUtils::checkAndReturnIsContinuationPossible(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,
+/* 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) {
diff --git a/native/jni/src/proximity_info_state_utils.h b/native/jni/src/proximity_info_state_utils.h
index a7f4a34..c9feb59 100644
--- a/native/jni/src/proximity_info_state_utils.h
+++ b/native/jni/src/proximity_info_state_utils.h
@@ -38,7 +38,8 @@
     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 int *const times, const int *const pointerIds,
+            const float verticalSweetSpotScale, 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,
@@ -84,6 +85,7 @@
             const int inputIndex, const int keyId);
     static void initGeometricDistanceInfos(const ProximityInfo *const proximityInfo,
             const int sampledInputSize, const int lastSavedInputSize,
+            const float verticalSweetSpotScale,
             const std::vector<int> *const sampledInputXs,
             const std::vector<int> *const sampledInputYs,
             std::vector<NearKeycodesSet> *SampledNearKeySets,
@@ -101,7 +103,7 @@
             const std::vector<int> *const sampledTimes,
             const std::vector<float> *const sampledSpeedRates,
             const std::vector<int> *const sampledBeelineSpeedPercentiles);
-    static bool checkAndReturnIsContinuationPossible(const int inputSize,
+    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,
@@ -118,6 +120,7 @@
 
     static float updateNearKeysDistances(const ProximityInfo *const proximityInfo,
             const float maxPointToKeyLength, const int x, const int y,
+            const float verticalSweetSpotScale,
             NearKeysDistanceMap *const currentNearKeysDistances);
     static bool isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
             const NearKeysDistanceMap *const prevNearKeysDistances,
@@ -130,7 +133,8 @@
             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 doSampling, const bool isLastPoint,
+            int y, const int time, const float verticalSweetSpotScale,
+            const bool doSampling, const bool isLastPoint,
             const float sumAngle, NearKeysDistanceMap *const currentNearKeysDistances,
             const NearKeysDistanceMap *const prevNearKeysDistances,
             const NearKeysDistanceMap *const prevPrevNearKeysDistances,
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.cpp b/native/jni/src/suggest/core/dicnode/dic_node.cpp
new file mode 100644
index 0000000..8c48c58
--- /dev/null
+++ b/native/jni/src/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 "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/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
new file mode 100644
index 0000000..32faae5
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -0,0 +1,582 @@
+/*
+ * 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 "char_utils.h"
+#include "defines.h"
+#include "dic_node_state.h"
+#include "dic_node_profiler.h"
+#include "dic_node_properties.h"
+#include "dic_node_release_listener.h"
+#include "digraph_utils.h"
+
+#if DEBUG_DICT
+#define LOGI_SHOW_ADD_COST_PROP \
+        do { char charBuf[50]; \
+        INTS_TO_CHARS(getOutputWordBuf(), getDepth(), 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(), getDepth(), charBuf); \
+        INTS_TO_CHARS(mDicNodeState.mDicNodeStatePrevWord.mPrevWord, \
+                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf); \
+        AKLOGI("#%8s, %5f, %5f, %5f, %5f, %s, %s, %d,,", header, \
+                getSpatialDistanceForScoring(), getLanguageDistanceForScoring(), \
+                getNormalizedCompoundDistance(), getRawLength(), prevWordCharBuf, charBuf, \
+                getInputIndex(0)); \
+        } 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 {
+    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() {}
+
+    // TODO: minimize arguments by looking binary_format
+    // 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);
+    }
+
+    // TODO: minimize arguments by looking binary_format
+    // Init for root with prevWordNodePos which is used for bigram
+    void initAsRoot(const int pos, const int childrenPos, const int childrenCount,
+            const int prevWordNodePos) {
+        mIsUsed = true;
+        mIsCachedForNextSuggestion = false;
+        mDicNodeProperties.init(
+                pos, 0, childrenPos, 0, 0, 0, childrenCount, 0, 0, false, false, true, 0, 0);
+        mDicNodeState.init(prevWordNodePos);
+        PROF_NODE_RESET(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);
+    }
+
+    // TODO: minimize arguments by looking binary_format
+    // Init for root with previous word
+    void initAsRootWithPreviousWord(DicNode *dicNode, const int pos, const int childrenPos,
+            const int childrenCount) {
+        mIsUsed = true;
+        mIsCachedForNextSuggestion = false;
+        mDicNodeProperties.init(
+                pos, 0, childrenPos, 0, 0, 0, childrenCount, 0, 0, false, false, true, 0, 0);
+        // 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.mPrevSpacePositions,
+                mDicNodeState.mDicNodeStateInput.getInputIndex(0) /* lastInputIndex */);
+        PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
+    }
+
+    // TODO: minimize arguments by looking binary_format
+    void initAsChild(DicNode *dicNode, const int pos, const uint8_t flags, const int childrenPos,
+            const int attributesPos, const int siblingPos, const int nodeCodePoint,
+            const int childrenCount, const int probability, const int bigramProbability,
+            const bool isTerminal, const bool hasMultipleChars, const bool hasChildren,
+            const uint16_t additionalSubwordLength, const int *additionalSubword) {
+        mIsUsed = true;
+        uint16_t newDepth = static_cast<uint16_t>(dicNode->getDepth() + 1);
+        mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
+        const uint16_t newLeavingDepth = static_cast<uint16_t>(
+                dicNode->mDicNodeProperties.getLeavingDepth() + additionalSubwordLength);
+        mDicNodeProperties.init(pos, flags, childrenPos, attributesPos, siblingPos, nodeCodePoint,
+                childrenCount, probability, bigramProbability, isTerminal, hasMultipleChars,
+                hasChildren, newDepth, newLeavingDepth);
+        mDicNodeState.init(&dicNode->mDicNodeState, additionalSubwordLength, additionalSubword);
+        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 getDepth() == 0;
+    }
+
+    bool hasChildren() const {
+        return mDicNodeProperties.hasChildren();
+    }
+
+    bool isLeavingNode() const {
+        ASSERT(getDepth() <= getLeavingDepth());
+        return getDepth() == getLeavingDepth();
+    }
+
+    AK_FORCE_INLINE bool isFirstLetter() const {
+        return getDepth() == 1;
+    }
+
+    bool isCached() const {
+        return mIsCachedForNextSuggestion;
+    }
+
+    void setCached() {
+        mIsCachedForNextSuggestion = true;
+    }
+
+    // Used to expand the node in DicNodeUtils
+    int getNodeTypedCodePoint() const {
+        return mDicNodeState.mDicNodeStateOutput.getCodePointAt(getDepth());
+    }
+
+    bool isImpossibleBigramWord() const {
+        const int probability = mDicNodeProperties.getProbability();
+        if (probability == 0) {
+            return true;
+        }
+        const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
+                - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
+        const int currentWordLen = getDepth();
+        return (prevWordLen == 1 && currentWordLen == 1);
+    }
+
+    bool isCapitalized() const {
+        const int c = getOutputWordBuf()[0];
+        return isAsciiUpper(c);
+    }
+
+    bool isFirstWord() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_VALID_WORD;
+    }
+
+    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();
+    }
+
+    // Used in DicNodeUtils
+    int getChildrenCount() const {
+        return mDicNodeProperties.getChildrenCount();
+    }
+
+    // Used in DicNodeUtils
+    int getProbability() const {
+        return mDicNodeProperties.getProbability();
+    }
+
+    AK_FORCE_INLINE bool isTerminalWordNode() const {
+        const bool isTerminalNodes = mDicNodeProperties.isTerminal();
+        const int currentNodeDepth = getDepth();
+        const int terminalNodeDepth = mDicNodeProperties.getLeavingDepth();
+        return isTerminalNodes && currentNodeDepth > 0 && currentNodeDepth == terminalNodeDepth;
+    }
+
+    bool shouldBeFilterdBySafetyNetForBigram() const {
+        const uint16_t currentDepth = getDepth();
+        const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
+                - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
+        return !(currentDepth > 0 && (currentDepth != 1 || prevWordLen != 1));
+    }
+
+    uint16_t getLeavingDepth() const {
+        return mDicNodeProperties.getLeavingDepth();
+    }
+
+    bool isTotalInputSizeExceedingLimit() const {
+        const int prevWordsLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
+        const int currentWordDepth = getDepth();
+        // 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 = getDepth();
+        DicNodeUtils::appendTwoWords(mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
+                   prevWordLength, getOutputWordBuf(), currentDepth, dest);
+        DUMP_WORD_AND_SCORE("OUTPUT");
+    }
+
+    void outputSpacePositionsResult(int *spaceIndices) const {
+        mDicNodeState.mDicNodeStatePrevWord.outputSpacePositions(spaceIndices);
+    }
+
+    bool hasMultipleWords() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() > 0;
+    }
+
+    float getProximityCorrectionCount() const {
+        return static_cast<float>(mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount());
+    }
+
+    float getEditCorrectionCount() const {
+        return static_cast<float>(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);
+    }
+
+    // Note that "cost" means delta for "distance" that is weighted.
+    float getTotalPrevWordsLanguageCost() const {
+        return mDicNodeState.mDicNodeStateScoring.getTotalPrevWordsLanguageCost();
+    }
+
+    // Used to commit input partially
+    int getPrevWordNodePos() const {
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos();
+    }
+
+    AK_FORCE_INLINE const int *getOutputWordBuf() const {
+        return mDicNodeState.mDicNodeStateOutput.mWordBuf;
+    }
+
+    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 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();
+    }
+
+    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();
+    }
+
+    uint8_t getFlags() const {
+        return mDicNodeProperties.getFlags();
+    }
+
+    int getAttributesPos() const {
+        return mDicNodeProperties.getAttributesPos();
+    }
+
+    inline uint16_t getDepth() const {
+        return mDicNodeProperties.getDepth();
+    }
+
+    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;
+        }
+        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 = getDepth();
+        const int depthDiff = right->getDepth() - 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 bool isEditCorrection,
+            const bool isProximityCorrection) {
+        if (DEBUG_GEO_FULL) {
+            LOGI_SHOW_ADD_COST_PROP;
+        }
+        mDicNodeState.mDicNodeStateScoring.addCost(spatialCost, languageCost, doNormalization,
+                inputSize, getTotalInputIndex(), isEditCorrection, isProximityCorrection);
+    }
+
+    // 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(DicNode_InputStateG *inputStateG) {
+        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/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
new file mode 100644
index 0000000..d3f28a8
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
@@ -0,0 +1,213 @@
+/*
+ * 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 "dic_node.h"
+#include "dic_node_release_listener.h"
+
+#define MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY 200
+
+namespace latinime {
+
+class DicNodePriorityQueue : public DicNodeReleaseListener {
+ public:
+    AK_FORCE_INLINE DicNodePriorityQueue()
+            : MAX_CAPACITY(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY),
+              mMaxSize(MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY), mDicNodesBuf(), mUnusedNodeIndices(),
+              mNextUnusedNodeId(0), mDicNodesQueue() {
+        mDicNodesBuf.resize(MAX_CAPACITY + 1);
+        mUnusedNodeIndices.resize(MAX_CAPACITY + 1);
+        reset();
+    }
+
+    // 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) {
+        mMaxSize = min(maxSize, MAX_CAPACITY);
+    }
+
+    AK_FORCE_INLINE void reset() {
+        clearAndResize(MAX_CAPACITY);
+    }
+
+    AK_FORCE_INLINE void clear() {
+        clearAndResize(mMaxSize);
+    }
+
+    AK_FORCE_INLINE void clearAndResize(const int maxSize) {
+        while (!mDicNodesQueue.empty()) {
+            mDicNodesQueue.pop();
+        }
+        setMaxSize(maxSize);
+        for (int i = 0; i < MAX_CAPACITY + 1; ++i) {
+            mDicNodesBuf[i].remove();
+            mDicNodesBuf[i].setReleaseListener(this);
+            mUnusedNodeIndices[i] = i == MAX_CAPACITY ? NOT_A_NODE_ID : static_cast<int>(i) + 1;
+        }
+        mNextUnusedNodeId = 0;
+    }
+
+    AK_FORCE_INLINE DicNode *newDicNode(DicNode *dicNode) {
+        DicNode *newNode = searchEmptyDicNode();
+        if (newNode) {
+            DicNodeUtils::initByCopy(dicNode, newNode);
+            return newNode;
+        }
+        return 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 < (MAX_CAPACITY + 1));
+    }
+
+    AK_FORCE_INLINE void dump() const {
+        AKLOGI("\n\n\n\n\n===========================");
+        for (int i = 0; i < MAX_CAPACITY + 1; ++i) {
+            if (mDicNodesBuf[i].isUsed()) {
+                mDicNodesBuf[i].dump("QUEUE: ");
+            }
+        }
+        AKLOGI("===========================\n\n\n\n\n");
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(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 MAX_CAPACITY;
+    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() {
+        // TODO: Currently O(n) but should be improved to O(1)
+        if (MAX_CAPACITY == 0) {
+            return 0;
+        }
+        if (mNextUnusedNodeId == NOT_A_NODE_ID) {
+            AKLOGI("No unused node found.");
+            for (int i = 0; i < MAX_CAPACITY + 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 < (MAX_CAPACITY + 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);
+    }
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_PRIORITY_QUEUE_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_profiler.h b/native/jni/src/suggest/core/dicnode/dic_node_profiler.h
new file mode 100644
index 0000000..90f75d0
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_profiler.h
@@ -0,0 +1,181 @@
+/*
+ * 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_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_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),
+              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 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 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/native/jni/src/suggest/core/dicnode/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/dic_node_properties.h
new file mode 100644
index 0000000..173ef35
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_properties.h
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+class DicNodeProperties {
+ public:
+    AK_FORCE_INLINE DicNodeProperties()
+            : mPos(0), mFlags(0), mChildrenPos(0), mAttributesPos(0), mSiblingPos(0),
+              mChildrenCount(0), mProbability(0), mBigramProbability(0), mNodeCodePoint(0),
+              mDepth(0), mLeavingDepth(0), mIsTerminal(false), mHasMultipleChars(false),
+              mHasChildren(false) {
+    }
+
+    virtual ~DicNodeProperties() {}
+
+    // Should be called only once per DicNode is initialized.
+    void init(const int pos, const uint8_t flags, const int childrenPos, const int attributesPos,
+            const int siblingPos, const int nodeCodePoint, const int childrenCount,
+            const int probability, const int bigramProbability, const bool isTerminal,
+            const bool hasMultipleChars, const bool hasChildren, const uint16_t depth,
+            const uint16_t terminalDepth) {
+        mPos = pos;
+        mFlags = flags;
+        mChildrenPos = childrenPos;
+        mAttributesPos = attributesPos;
+        mSiblingPos = siblingPos;
+        mNodeCodePoint = nodeCodePoint;
+        mChildrenCount = childrenCount;
+        mProbability = probability;
+        mBigramProbability = bigramProbability;
+        mIsTerminal = isTerminal;
+        mHasMultipleChars = hasMultipleChars;
+        mHasChildren = hasChildren;
+        mDepth = depth;
+        mLeavingDepth = terminalDepth;
+    }
+
+    // Init for copy
+    void init(const DicNodeProperties *const nodeProp) {
+        mPos = nodeProp->mPos;
+        mFlags = nodeProp->mFlags;
+        mChildrenPos = nodeProp->mChildrenPos;
+        mAttributesPos = nodeProp->mAttributesPos;
+        mSiblingPos = nodeProp->mSiblingPos;
+        mNodeCodePoint = nodeProp->mNodeCodePoint;
+        mChildrenCount = nodeProp->mChildrenCount;
+        mProbability = nodeProp->mProbability;
+        mBigramProbability = nodeProp->mBigramProbability;
+        mIsTerminal = nodeProp->mIsTerminal;
+        mHasMultipleChars = nodeProp->mHasMultipleChars;
+        mHasChildren = nodeProp->mHasChildren;
+        mDepth = nodeProp->mDepth;
+        mLeavingDepth = nodeProp->mLeavingDepth;
+    }
+
+    // Init as passing child
+    void init(const DicNodeProperties *const nodeProp, const int codePoint) {
+        mPos = nodeProp->mPos;
+        mFlags = nodeProp->mFlags;
+        mChildrenPos = nodeProp->mChildrenPos;
+        mAttributesPos = nodeProp->mAttributesPos;
+        mSiblingPos = nodeProp->mSiblingPos;
+        mNodeCodePoint = codePoint; // Overwrite the node char of a passing child
+        mChildrenCount = nodeProp->mChildrenCount;
+        mProbability = nodeProp->mProbability;
+        mBigramProbability = nodeProp->mBigramProbability;
+        mIsTerminal = nodeProp->mIsTerminal;
+        mHasMultipleChars = nodeProp->mHasMultipleChars;
+        mHasChildren = nodeProp->mHasChildren;
+        mDepth = nodeProp->mDepth + 1; // Increment the depth of a passing child
+        mLeavingDepth = nodeProp->mLeavingDepth;
+    }
+
+    int getPos() const {
+        return mPos;
+    }
+
+    uint8_t getFlags() const {
+        return mFlags;
+    }
+
+    int getChildrenPos() const {
+        return mChildrenPos;
+    }
+
+    int getAttributesPos() const {
+        return mAttributesPos;
+    }
+
+    int getChildrenCount() const {
+        return mChildrenCount;
+    }
+
+    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 hasMultipleChars() const {
+        return mHasMultipleChars;
+    }
+
+    bool hasChildren() const {
+        return mChildrenCount > 0 || mDepth != mLeavingDepth;
+    }
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+
+    // Not used
+    int getSiblingPos() const {
+        return mSiblingPos;
+    }
+
+    int mPos;
+    uint8_t mFlags;
+    int mChildrenPos;
+    int mAttributesPos;
+    int mSiblingPos;
+    int mChildrenCount;
+    int mProbability;
+    int mBigramProbability; // not used for now
+    int mNodeCodePoint;
+    uint16_t mDepth;
+    uint16_t mLeavingDepth;
+    bool mIsTerminal;
+    bool mHasMultipleChars;
+    bool mHasChildren;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_PROPERTIES_H
diff --git a/native/jni/src/suggest/typing_suggest.cpp b/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h
similarity index 62%
copy from native/jni/src/suggest/typing_suggest.cpp
copy to native/jni/src/suggest/core/dicnode/dic_node_release_listener.h
index 56bd5b6..2a81c3c 100644
--- a/native/jni/src/suggest/typing_suggest.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h
@@ -14,12 +14,20 @@
  * limitations under the License.
  */
 
-#include "typing_suggest.h"
+#ifndef LATINIME_DIC_NODE_RELEASE_LISTENER_H
+#define LATINIME_DIC_NODE_RELEASE_LISTENER_H
+
+#include "defines.h"
 
 namespace latinime {
-    SuggestInterface *(*TypingSuggest::sTypingSuggestFactoryMethod)() = 0;
 
-    TypingSuggest::~TypingSuggest() {
-        delete mSuggestInterface;
-    }
+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/native/jni/src/suggest/core/dicnode/dic_node_state.h b/native/jni/src/suggest/core/dicnode/dic_node_state.h
new file mode 100644
index 0000000..239b63c
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_state.h
@@ -0,0 +1,71 @@
+/*
+ * 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 "dic_node_state_input.h"
+#include "dic_node_state_output.h"
+#include "dic_node_state_prevword.h"
+#include "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 subword
+    void init(const DicNodeState *const src, const uint16_t additionalSubwordLength,
+            const int *const additionalSubword) {
+        init(src);
+        mDicNodeStateOutput.addSubword(additionalSubwordLength, additionalSubword);
+    }
+
+ 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/native/jni/src/suggest/core/dicnode/dic_node_state_input.h b/native/jni/src/suggest/core/dicnode/dic_node_state_input.h
new file mode 100644
index 0000000..7ad3e3e
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/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/native/jni/src/suggest/core/dicnode/dic_node_state_output.h b/native/jni/src/suggest/core/dicnode/dic_node_state_output.h
new file mode 100644
index 0000000..1d4f50a
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_state_output.h
@@ -0,0 +1,75 @@
+/*
+ * 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() : mOutputtedLength(0) {
+        init();
+    }
+
+    virtual ~DicNodeStateOutput() {}
+
+    void init() {
+        mOutputtedLength = 0;
+        mWordBuf[0] = 0;
+    }
+
+    void init(const DicNodeStateOutput *const stateOutput) {
+        memcpy(mWordBuf, stateOutput->mWordBuf,
+                stateOutput->mOutputtedLength * sizeof(mWordBuf[0]));
+        mOutputtedLength = stateOutput->mOutputtedLength;
+        if (mOutputtedLength < MAX_WORD_LENGTH) {
+            mWordBuf[mOutputtedLength] = 0;
+        }
+    }
+
+    void addSubword(const uint16_t additionalSubwordLength, const int *const additionalSubword) {
+        if (additionalSubword) {
+            memcpy(&mWordBuf[mOutputtedLength], additionalSubword,
+                    additionalSubwordLength * sizeof(mWordBuf[0]));
+            mOutputtedLength = static_cast<uint16_t>(mOutputtedLength + additionalSubwordLength);
+            if (mOutputtedLength < MAX_WORD_LENGTH) {
+                mWordBuf[mOutputtedLength] = 0;
+            }
+        }
+    }
+
+    // TODO: Remove
+    int getCodePointAt(const int id) const {
+        return mWordBuf[id];
+    }
+
+    // TODO: Move to private
+    int mWordBuf[MAX_WORD_LENGTH];
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+    uint16_t mOutputtedLength;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_OUTPUT_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h
new file mode 100644
index 0000000..e3b892b
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h
@@ -0,0 +1,156 @@
+/*
+ * 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 "dic_node_utils.h"
+
+namespace latinime {
+
+class DicNodeStatePrevWord {
+ public:
+    AK_FORCE_INLINE DicNodeStatePrevWord()
+            : mPrevWordCount(0), mPrevWordLength(0), mPrevWordStart(0), mPrevWordProbability(0),
+              mPrevWordNodePos(0) {
+        memset(mPrevWord, 0, sizeof(mPrevWord));
+        memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
+    }
+
+    virtual ~DicNodeStatePrevWord() {}
+
+    void init() {
+        mPrevWordLength = 0;
+        mPrevWordCount = 0;
+        mPrevWordStart = 0;
+        mPrevWordProbability = -1;
+        mPrevWordNodePos = NOT_VALID_WORD;
+        memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
+    }
+
+    void init(const int prevWordNodePos) {
+        mPrevWordLength = 0;
+        mPrevWordCount = 0;
+        mPrevWordStart = 0;
+        mPrevWordProbability = -1;
+        mPrevWordNodePos = prevWordNodePos;
+        memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
+    }
+
+    // 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;
+        memcpy(mPrevWord, prevWord->mPrevWord, prevWord->mPrevWordLength * sizeof(mPrevWord[0]));
+        memcpy(mPrevSpacePositions, prevWord->mPrevSpacePositions, sizeof(mPrevSpacePositions));
+    }
+
+    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 *const prevSpacePositions,
+            const int lastInputIndex) {
+        mPrevWordCount = prevWordCount;
+        mPrevWordProbability = prevWordProbability;
+        mPrevWordNodePos = prevWordNodePos;
+        const int twoWordsLen =
+                DicNodeUtils::appendTwoWords(src0, length0, src1, length1, mPrevWord);
+        mPrevWord[twoWordsLen] = KEYCODE_SPACE;
+        mPrevWordStart = length0;
+        mPrevWordLength = static_cast<int16_t>(twoWordsLen + 1);
+        memcpy(mPrevSpacePositions, prevSpacePositions, sizeof(mPrevSpacePositions));
+        mPrevSpacePositions[mPrevWordCount - 1] = lastInputIndex;
+    }
+
+    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 outputSpacePositions(int *spaceIndices) const {
+        // Convert uint16_t to int
+        for (int i = 0; i < MAX_RESULTS; i++) {
+            spaceIndices[i] = mPrevSpacePositions[i];
+        }
+    }
+
+    // TODO: remove
+    int16_t getPrevWordLength() const {
+        return mPrevWordLength;
+    }
+
+    int16_t getPrevWordCount() const {
+        return mPrevWordCount;
+    }
+
+    int16_t getPrevWordStart() const {
+        return mPrevWordStart;
+    }
+
+    int16_t getPrevWordProbability() const {
+        return mPrevWordProbability;
+    }
+
+    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];
+    // TODO: Move to private
+    int mPrevSpacePositions[MAX_RESULTS];
+
+ 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;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_PREVWORD_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h b/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h
new file mode 100644
index 0000000..8902d31
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h
@@ -0,0 +1,189 @@
+/*
+ * 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 "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),
+              mTotalPrevWordsLanguageCost(0.0f), mRawLength(0.0f) {
+    }
+
+    virtual ~DicNodeStateScoring() {}
+
+    void init() {
+        mEditCorrectionCount = 0;
+        mProximityCorrectionCount = 0;
+        mNormalizedCompoundDistance = 0.0f;
+        mSpatialDistance = 0.0f;
+        mLanguageDistance = 0.0f;
+        mTotalPrevWordsLanguageCost = 0.0f;
+        mRawLength = 0.0f;
+        mDoubleLetterLevel = NOT_A_DOUBLE_LETTER;
+        mDigraphIndex = DigraphUtils::NOT_A_DIGRAPH_INDEX;
+    }
+
+    AK_FORCE_INLINE void init(const DicNodeStateScoring *const scoring) {
+        mEditCorrectionCount = scoring->mEditCorrectionCount;
+        mProximityCorrectionCount = scoring->mProximityCorrectionCount;
+        mNormalizedCompoundDistance = scoring->mNormalizedCompoundDistance;
+        mSpatialDistance = scoring->mSpatialDistance;
+        mLanguageDistance = scoring->mLanguageDistance;
+        mTotalPrevWordsLanguageCost = scoring->mTotalPrevWordsLanguageCost;
+        mRawLength = scoring->mRawLength;
+        mDoubleLetterLevel = scoring->mDoubleLetterLevel;
+        mDigraphIndex = scoring->mDigraphIndex;
+    }
+
+    void addCost(const float spatialCost, const float languageCost, const bool doNormalization,
+            const int inputSize, const int totalInputIndex, const bool isEditCorrection,
+            const bool isProximityCorrection) {
+        addDistance(spatialCost, languageCost, doNormalization, inputSize, totalInputIndex);
+        if (isEditCorrection) {
+            ++mEditCorrectionCount;
+        }
+        if (isProximityCorrection) {
+            ++mProximityCorrectionCount;
+        }
+        if (languageCost > 0.0f) {
+            setTotalPrevWordsLanguageCost(mTotalPrevWordsLanguageCost + languageCost);
+        }
+    }
+
+    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;
+    }
+
+    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;
+        }
+    }
+
+    float getTotalPrevWordsLanguageCost() const {
+        return mTotalPrevWordsLanguageCost;
+    }
+
+ 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 mTotalPrevWordsLanguageCost;
+    float mRawLength;
+
+    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));
+        }
+    }
+
+    //TODO: remove
+    AK_FORCE_INLINE void setTotalPrevWordsLanguageCost(float totalPrevWordsLanguageCost) {
+        mTotalPrevWordsLanguageCost = totalPrevWordsLanguageCost;
+    }
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_SCORING_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
new file mode 100644
index 0000000..031e706
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -0,0 +1,335 @@
+/*
+ * 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 <cstring>
+#include <vector>
+
+#include "binary_format.h"
+#include "dic_node.h"
+#include "dic_node_utils.h"
+#include "dic_node_vector.h"
+#include "proximity_info.h"
+#include "proximity_info_state.h"
+
+namespace latinime {
+
+///////////////////////////////
+// Node initialization utils //
+///////////////////////////////
+
+/* static */ void DicNodeUtils::initAsRoot(const int rootPos, const uint8_t *const dicRoot,
+        const int prevWordNodePos, DicNode *newRootNode) {
+    int curPos = rootPos;
+    const int pos = curPos;
+    const int childrenCount = BinaryFormat::getGroupCountAndForwardPointer(dicRoot, &curPos);
+    const int childrenPos = curPos;
+    newRootNode->initAsRoot(pos, childrenPos, childrenCount, prevWordNodePos);
+}
+
+/*static */ void DicNodeUtils::initAsRootWithPreviousWord(const int rootPos,
+        const uint8_t *const dicRoot, DicNode *prevWordLastNode, DicNode *newRootNode) {
+    int curPos = rootPos;
+    const int pos = curPos;
+    const int childrenCount = BinaryFormat::getGroupCountAndForwardPointer(dicRoot, &curPos);
+    const int childrenPos = curPos;
+    newRootNode->initAsRootWithPreviousWord(prevWordLastNode, pos, childrenPos, childrenCount);
+}
+
+/* static */ void DicNodeUtils::initByCopy(DicNode *srcNode, DicNode *destNode) {
+    destNode->initByCopy(srcNode);
+}
+
+///////////////////////////////////
+// Traverse node expansion utils //
+///////////////////////////////////
+
+/* static */ void DicNodeUtils::createAndGetPassingChildNode(DicNode *dicNode,
+        const ProximityInfoState *pInfoState, const int pointIndex, const bool exactOnly,
+        DicNodeVector *childDicNodes) {
+    // Passing multiple chars node. No need to traverse child
+    const int codePoint = dicNode->getNodeTypedCodePoint();
+    const int baseLowerCaseCodePoint = toBaseLowerCase(codePoint);
+    const bool isMatch = isMatchedNodeCodePoint(pInfoState, pointIndex, exactOnly, codePoint);
+    if (isMatch || isIntentionalOmissionCodePoint(baseLowerCaseCodePoint)) {
+        childDicNodes->pushPassingChild(dicNode);
+    }
+}
+
+/* static */ int DicNodeUtils::createAndGetLeavingChildNode(DicNode *dicNode, int pos,
+        const uint8_t *const dicRoot, const int terminalDepth, const ProximityInfoState *pInfoState,
+        const int pointIndex, const bool exactOnly, const std::vector<int> *const codePointsFilter,
+        const ProximityInfo *const pInfo, DicNodeVector *childDicNodes) {
+    int nextPos = pos;
+    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(dicRoot, &pos);
+    const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags));
+    const bool isTerminal = (0 != (BinaryFormat::FLAG_IS_TERMINAL & flags));
+    const bool hasChildren = BinaryFormat::hasChildrenInFlags(flags);
+
+    int codePoint = BinaryFormat::getCodePointAndForwardPointer(dicRoot, &pos);
+    ASSERT(NOT_A_CODE_POINT != codePoint);
+    const int nodeCodePoint = codePoint;
+    // TODO: optimize this
+    int additionalWordBuf[MAX_WORD_LENGTH];
+    uint16_t additionalSubwordLength = 0;
+    additionalWordBuf[additionalSubwordLength++] = codePoint;
+
+    do {
+        const int nextCodePoint = hasMultipleChars
+                ? BinaryFormat::getCodePointAndForwardPointer(dicRoot, &pos) : NOT_A_CODE_POINT;
+        const bool isLastChar = (NOT_A_CODE_POINT == nextCodePoint);
+        if (!isLastChar) {
+            additionalWordBuf[additionalSubwordLength++] = nextCodePoint;
+        }
+        codePoint = nextCodePoint;
+    } while (NOT_A_CODE_POINT != codePoint);
+
+    const int probability =
+            isTerminal ? BinaryFormat::readProbabilityWithoutMovingPointer(dicRoot, pos) : -1;
+    pos = BinaryFormat::skipProbability(flags, pos);
+    int childrenPos = hasChildren ? BinaryFormat::readChildrenPosition(dicRoot, flags, pos) : 0;
+    const int attributesPos = BinaryFormat::skipChildrenPosition(flags, pos);
+    const int siblingPos = BinaryFormat::skipChildrenPosAndAttributes(dicRoot, flags, pos);
+
+    if (isDicNodeFilteredOut(nodeCodePoint, pInfo, codePointsFilter)) {
+        return siblingPos;
+    }
+    if (!isMatchedNodeCodePoint(pInfoState, pointIndex, exactOnly, nodeCodePoint)) {
+        return siblingPos;
+    }
+    const int childrenCount = hasChildren
+            ? BinaryFormat::getGroupCountAndForwardPointer(dicRoot, &childrenPos) : 0;
+    childDicNodes->pushLeavingChild(dicNode, nextPos, flags, childrenPos, attributesPos, siblingPos,
+            nodeCodePoint, childrenCount, probability, -1 /* bigramProbability */, isTerminal,
+            hasMultipleChars, hasChildren, additionalSubwordLength, additionalWordBuf);
+    return siblingPos;
+}
+
+/* static */ bool DicNodeUtils::isDicNodeFilteredOut(const int nodeCodePoint,
+        const ProximityInfo *const pInfo, const std::vector<int> *const codePointsFilter) {
+    const int filterSize = codePointsFilter ? codePointsFilter->size() : 0;
+    if (filterSize <= 0) {
+        return false;
+    }
+    if (pInfo && (pInfo->getKeyIndexOf(nodeCodePoint) == NOT_AN_INDEX
+            || isIntentionalOmissionCodePoint(nodeCodePoint))) {
+        // If normalized nodeCodePoint is not on the keyboard or skippable, this child is never
+        // filtered.
+        return false;
+    }
+    const int lowerCodePoint = toLowerCase(nodeCodePoint);
+    const int baseLowerCodePoint = toBaseCodePoint(lowerCodePoint);
+    // TODO: Avoid linear search
+    for (int i = 0; i < filterSize; ++i) {
+        // Checking if a normalized code point is in filter characters when pInfo is not
+        // null. When pInfo is null, nodeCodePoint is used to check filtering without
+        // normalizing.
+        if ((pInfo && ((*codePointsFilter)[i] == lowerCodePoint
+                || (*codePointsFilter)[i] == baseLowerCodePoint))
+                        || (!pInfo && (*codePointsFilter)[i] == nodeCodePoint)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/* static */ void DicNodeUtils::createAndGetAllLeavingChildNodes(DicNode *dicNode,
+        const uint8_t *const dicRoot, const ProximityInfoState *pInfoState, const int pointIndex,
+        const bool exactOnly, const std::vector<int> *const codePointsFilter,
+        const ProximityInfo *const pInfo, DicNodeVector *childDicNodes) {
+    const int terminalDepth = dicNode->getLeavingDepth();
+    const int childCount = dicNode->getChildrenCount();
+    int nextPos = dicNode->getChildrenPos();
+    for (int i = 0; i < childCount; i++) {
+        const int filterSize = codePointsFilter ? codePointsFilter->size() : 0;
+        nextPos = createAndGetLeavingChildNode(dicNode, nextPos, dicRoot, terminalDepth, pInfoState,
+                pointIndex, exactOnly, codePointsFilter, pInfo, childDicNodes);
+        if (!pInfo && filterSize > 0 && childDicNodes->exceeds(filterSize)) {
+            // All code points have been found.
+            break;
+        }
+    }
+}
+
+/* static */ void DicNodeUtils::getAllChildDicNodes(DicNode *dicNode, const uint8_t *const dicRoot,
+        DicNodeVector *childDicNodes) {
+    getProximityChildDicNodes(dicNode, dicRoot, 0, 0, false, childDicNodes);
+}
+
+/* static */ void DicNodeUtils::getProximityChildDicNodes(DicNode *dicNode,
+        const uint8_t *const dicRoot, const ProximityInfoState *pInfoState, const int pointIndex,
+        bool exactOnly, DicNodeVector *childDicNodes) {
+    if (dicNode->isTotalInputSizeExceedingLimit()) {
+        return;
+    }
+    if (!dicNode->isLeavingNode()) {
+        DicNodeUtils::createAndGetPassingChildNode(dicNode, pInfoState, pointIndex, exactOnly,
+                childDicNodes);
+    } else {
+        DicNodeUtils::createAndGetAllLeavingChildNodes(dicNode, dicRoot, pInfoState, pointIndex,
+                exactOnly, 0 /* codePointsFilter */, 0 /* pInfo */,
+                childDicNodes);
+    }
+}
+
+///////////////////
+// Scoring utils //
+///////////////////
+/**
+ * Computes the combined bigram / unigram cost for the given dicNode.
+ */
+/* static */ float DicNodeUtils::getBigramNodeImprobability(const uint8_t *const dicRoot,
+        const DicNode *const node, hash_map_compat<int, int16_t> *bigramCacheMap) {
+    if (node->isImpossibleBigramWord()) {
+        return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+    }
+    const int probability = getBigramNodeProbability(dicRoot, node, bigramCacheMap);
+    // 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 uint8_t *const dicRoot,
+        const DicNode *const node, hash_map_compat<int, int16_t> *bigramCacheMap) {
+    const int unigramProbability = node->getProbability();
+    const int encodedDiffOfBigramProbability =
+            getBigramNodeEncodedDiffProbability(dicRoot, node, bigramCacheMap);
+    if (NOT_A_PROBABILITY == encodedDiffOfBigramProbability) {
+        return backoff(unigramProbability);
+    }
+    return BinaryFormat::computeProbabilityForBigram(
+            unigramProbability, encodedDiffOfBigramProbability);
+}
+
+///////////////////////////////////////
+// Bigram / Unigram dictionary utils //
+///////////////////////////////////////
+
+/* static */ int16_t DicNodeUtils::getBigramNodeEncodedDiffProbability(const uint8_t *const dicRoot,
+        const DicNode *const node, hash_map_compat<int, int16_t> *bigramCacheMap) {
+    const int wordPos = node->getPos();
+    const int prevWordPos = node->getPrevWordPos();
+    return getBigramProbability(dicRoot, prevWordPos, wordPos, bigramCacheMap);
+}
+
+// TODO: Move this to BigramDictionary
+/* static */ int16_t DicNodeUtils::getBigramProbability(const uint8_t *const dicRoot, int pos,
+        const int nextPos, hash_map_compat<int, int16_t> *bigramCacheMap) {
+    // TODO: this is painfully slow compared to the method used in the previous version of the
+    // algorithm. Switch to that method.
+    if (NOT_VALID_WORD == pos) return NOT_A_PROBABILITY;
+    if (NOT_VALID_WORD == nextPos) return NOT_A_PROBABILITY;
+
+    // Create a hash code for the given node pair (based on Josh Bloch's effective Java).
+    // TODO: Use a real hash map data structure that deals with collisions.
+    int hash = 17;
+    hash = hash * 31 + pos;
+    hash = hash * 31 + nextPos;
+
+    hash_map_compat<int, int16_t>::const_iterator mapPos = bigramCacheMap->find(hash);
+    if (mapPos != bigramCacheMap->end()) {
+        return mapPos->second;
+    }
+    if (NOT_VALID_WORD == pos) {
+        return NOT_A_PROBABILITY;
+    }
+    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(dicRoot, &pos);
+    if (0 == (flags & BinaryFormat::FLAG_HAS_BIGRAMS)) {
+        return NOT_A_PROBABILITY;
+    }
+    if (0 == (flags & BinaryFormat::FLAG_HAS_MULTIPLE_CHARS)) {
+        BinaryFormat::getCodePointAndForwardPointer(dicRoot, &pos);
+    } else {
+        pos = BinaryFormat::skipOtherCharacters(dicRoot, pos);
+    }
+    pos = BinaryFormat::skipChildrenPosition(flags, pos);
+    pos = BinaryFormat::skipProbability(flags, pos);
+    uint8_t bigramFlags;
+    int count = 0;
+    do {
+        bigramFlags = BinaryFormat::getFlagsAndForwardPointer(dicRoot, &pos);
+        const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(dicRoot,
+                bigramFlags, &pos);
+        if (bigramPos == nextPos) {
+            const int16_t probability = BinaryFormat::MASK_ATTRIBUTE_PROBABILITY & bigramFlags;
+            if (static_cast<int>(bigramCacheMap->size()) < MAX_BIGRAM_MAP_SIZE) {
+                (*bigramCacheMap)[hash] = probability;
+            }
+            return probability;
+        }
+        count++;
+    } while ((0 != (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags))
+            && count < MAX_BIGRAMS_CONSIDERED_PER_CONTEXT);
+    if (static_cast<int>(bigramCacheMap->size()) < MAX_BIGRAM_MAP_SIZE) {
+        // TODO: does this -1 mean NOT_VALID_WORD?
+        (*bigramCacheMap)[hash] = -1;
+    }
+    return NOT_A_PROBABILITY;
+}
+
+/* static */ int DicNodeUtils::getWordPos(const uint8_t *const dicRoot, const int *word,
+        const int wordLength) {
+    if (!word) {
+        return NOT_VALID_WORD;
+    }
+    return BinaryFormat::getTerminalPosition(
+            dicRoot, word, wordLength, false /* forceLowerCaseSearch */);
+}
+
+/* static */ bool DicNodeUtils::isMatchedNodeCodePoint(const ProximityInfoState *pInfoState,
+        const int pointIndex, const bool exactOnly, const int nodeCodePoint) {
+    if (!pInfoState) {
+        return true;
+    }
+    if (exactOnly) {
+        return pInfoState->getPrimaryCodePointAt(pointIndex) == nodeCodePoint;
+    }
+    const ProximityType matchedId = pInfoState->getProximityType(pointIndex, nodeCodePoint,
+            true /* checkProximityChars */);
+    return isProximityChar(matchedId);
+}
+
+////////////////
+// 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 - 1);
+    memcpy(&dest[actualLength0], src1, actualLength1 * sizeof(dest[0]));
+    return actualLength0 + actualLength1;
+}
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.h b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
new file mode 100644
index 0000000..15f9730
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
@@ -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.
+ */
+
+#ifndef LATINIME_DIC_NODE_UTILS_H
+#define LATINIME_DIC_NODE_UTILS_H
+
+#include <stdint.h>
+#include <vector>
+
+#include "defines.h"
+#include "hash_map_compat.h"
+
+namespace latinime {
+
+class DicNode;
+class DicNodeVector;
+class ProximityInfo;
+class ProximityInfoState;
+
+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 int rootPos, const uint8_t *const dicRoot,
+            const int prevWordNodePos, DicNode *newRootNode);
+    static void initAsRootWithPreviousWord(const int rootPos, const uint8_t *const dicRoot,
+            DicNode *prevWordLastNode, DicNode *newRootNode);
+    static void initByCopy(DicNode *srcNode, DicNode *destNode);
+    static void getAllChildDicNodes(DicNode *dicNode, const uint8_t *const dicRoot,
+            DicNodeVector *childDicNodes);
+    static int getWordPos(const uint8_t *const dicRoot, const int *word, const int prevWordLength);
+    static float getBigramNodeImprobability(const uint8_t *const dicRoot,
+            const DicNode *const node, hash_map_compat<int, int16_t> *const bigramCacheMap);
+    static bool isDicNodeFilteredOut(const int nodeCodePoint, const ProximityInfo *const pInfo,
+            const std::vector<int> *const codePointsFilter);
+    // TODO: Move to private
+    static void getProximityChildDicNodes(DicNode *dicNode, const uint8_t *const dicRoot,
+            const ProximityInfoState *pInfoState, const int pointIndex, bool exactOnly,
+            DicNodeVector *childDicNodes);
+
+    // TODO: Move to proximity info
+    static bool isProximityChar(ProximityType type) {
+        return type == MATCH_CHAR || type == PROXIMITY_CHAR || type == ADDITIONAL_PROXIMITY_CHAR;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodeUtils);
+    // Max cache size for the space omission error correction bigram lookup
+    static const int MAX_BIGRAM_MAP_SIZE = 20000;
+    // Max number of bigrams to look up
+    static const int MAX_BIGRAMS_CONSIDERED_PER_CONTEXT = 500;
+
+    static int getBigramNodeProbability(const uint8_t *const dicRoot, const DicNode *const node,
+            hash_map_compat<int, int16_t> *bigramCacheMap);
+    static int16_t getBigramNodeEncodedDiffProbability(const uint8_t *const dicRoot,
+            const DicNode *const node, hash_map_compat<int, int16_t> *bigramCacheMap);
+    static void createAndGetPassingChildNode(DicNode *dicNode, const ProximityInfoState *pInfoState,
+            const int pointIndex, const bool exactOnly, DicNodeVector *childDicNodes);
+    static void createAndGetAllLeavingChildNodes(DicNode *dicNode, const uint8_t *const dicRoot,
+            const ProximityInfoState *pInfoState, const int pointIndex, const bool exactOnly,
+            const std::vector<int> *const codePointsFilter,
+            const ProximityInfo *const pInfo, DicNodeVector *childDicNodes);
+    static int createAndGetLeavingChildNode(DicNode *dicNode, int pos, const uint8_t *const dicRoot,
+            const int terminalDepth, const ProximityInfoState *pInfoState, const int pointIndex,
+            const bool exactOnly, const std::vector<int> *const codePointsFilter,
+            const ProximityInfo *const pInfo, DicNodeVector *childDicNodes);
+    static int16_t getBigramProbability(const uint8_t *const dicRoot, int pos, const int nextPos,
+            hash_map_compat<int, int16_t> *bigramCacheMap);
+
+    // TODO: Move to proximity info
+    static bool isMatchedNodeCodePoint(const ProximityInfoState *pInfoState, const int pointIndex,
+            const bool exactOnly, const int nodeCodePoint);
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_UTILS_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_vector.h b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
new file mode 100644
index 0000000..ca07eda
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
@@ -0,0 +1,95 @@
+/*
+ * 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 "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(DicNode *dicNode, const int pos, const uint8_t flags,
+            const int childrenPos, const int attributesPos, const int siblingPos,
+            const int nodeCodePoint, const int childrenCount, const int probability,
+            const int bigramProbability, const bool isTerminal, const bool hasMultipleChars,
+            const bool hasChildren, const uint16_t additionalSubwordLength,
+            const int *additionalSubword) {
+        ASSERT(!mLock);
+        mDicNodes.push_back(mEmptyNode);
+        mDicNodes.back().initAsChild(dicNode, pos, flags, childrenPos, attributesPos, siblingPos,
+                nodeCodePoint, childrenCount, probability, -1 /* bigramProbability */, isTerminal,
+                hasMultipleChars, hasChildren, additionalSubwordLength, additionalSubword);
+    }
+
+    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/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
new file mode 100644
index 0000000..b9a6078
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
@@ -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.
+ */
+
+#include <list>
+
+#include "defines.h"
+#include "dic_node_priority_queue.h"
+#include "dic_node_utils.h"
+#include "dic_nodes_cache.h"
+
+namespace latinime {
+
+/**
+ * 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/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
new file mode 100644
index 0000000..a62aa42
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
@@ -0,0 +1,185 @@
+/*
+ * 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 "dic_node_priority_queue.h"
+
+#define INITIAL_QUEUE_ID_ACTIVE 0
+#define INITIAL_QUEUE_ID_NEXT_ACTIVE 1
+#define INITIAL_QUEUE_ID_TERMINAL 2
+#define INITIAL_QUEUE_ID_CACHE_FOR_CONTINUOUS_SUGGESTION 3
+#define PRIORITY_QUEUES_SIZE 4
+
+namespace latinime {
+
+class DicNode;
+
+/**
+ * Class for controlling dicNode search priority queue and lexicon trie traversal.
+ */
+class DicNodesCache {
+ public:
+    AK_FORCE_INLINE DicNodesCache()
+            : mActiveDicNodes(&mDicNodePriorityQueues[INITIAL_QUEUE_ID_ACTIVE]),
+              mNextActiveDicNodes(&mDicNodePriorityQueues[INITIAL_QUEUE_ID_NEXT_ACTIVE]),
+              mTerminalDicNodes(&mDicNodePriorityQueues[INITIAL_QUEUE_ID_TERMINAL]),
+              mCachedDicNodesForContinuousSuggestion(
+                      &mDicNodePriorityQueues[INITIAL_QUEUE_ID_CACHE_FOR_CONTINUOUS_SUGGESTION]),
+              mInputIndex(0), mLastCachedInputIndex(0) {
+    }
+
+    AK_FORCE_INLINE virtual ~DicNodesCache() {}
+
+    AK_FORCE_INLINE void reset(const int nextActiveSize, const int terminalSize) {
+        mInputIndex = 0;
+        mLastCachedInputIndex = 0;
+        mActiveDicNodes->reset();
+        mNextActiveDicNodes->clearAndResize(nextActiveSize);
+        mTerminalDicNodes->clearAndResize(terminalSize);
+        mCachedDicNodesForContinuousSuggestion->reset();
+    }
+
+    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 void resetTemporaryCaches() {
+        mActiveDicNodes->clear();
+        mNextActiveDicNodes->clear();
+        mTerminalDicNodes->clear();
+    }
+
+    DicNodePriorityQueue mDicNodePriorityQueues[PRIORITY_QUEUES_SIZE];
+    // Active dicNodes currently being expanded.
+    DicNodePriorityQueue *mActiveDicNodes;
+    // Next dicNodes to be expanded.
+    DicNodePriorityQueue *mNextActiveDicNodes;
+    // Current top terminal dicNodes.
+    DicNodePriorityQueue *mTerminalDicNodes;
+    // Cached dicNodes used for continuous suggestion.
+    DicNodePriorityQueue *mCachedDicNodesForContinuousSuggestion;
+    int mInputIndex;
+    int mLastCachedInputIndex;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODES_CACHE_H
diff --git a/native/jni/src/suggest/core/dictionary/shortcut_utils.h b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
new file mode 100644
index 0000000..c411408
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
@@ -0,0 +1,65 @@
+/*
+ * 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 "terminal_attributes.h"
+
+namespace latinime {
+
+class ShortcutUtils {
+ public:
+    static int outputShortcuts(const TerminalAttributes *const terminalAttributes,
+            int outputWordIndex, const int finalScore, int *const outputCodePoints,
+            int *const frequencies, int *const outputTypes, const bool sameAsTyped) {
+        TerminalAttributes::ShortcutIterator iterator = terminalAttributes->getShortcutIterator();
+        while (iterator.hasNextShortcutTarget() && outputWordIndex < MAX_RESULTS) {
+            int shortcutTarget[MAX_WORD_LENGTH];
+            int shortcutProbability;
+            const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
+                    MAX_WORD_LENGTH, shortcutTarget, &shortcutProbability);
+            int shortcutScore;
+            int kind;
+            if (shortcutProbability == BinaryFormat::WHITELIST_SHORTCUT_PROBABILITY
+                    && 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_CORRECTION;
+            }
+            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/native/jni/src/suggest/core/policy/scoring.h b/native/jni/src/suggest/core/policy/scoring.h
new file mode 100644
index 0000000..b8c10e2
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/scoring.h
@@ -0,0 +1,57 @@
+/*
+ * 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/native/jni/src/suggest/core/policy/suggest_policy.h b/native/jni/src/suggest/core/policy/suggest_policy.h
new file mode 100644
index 0000000..885e214
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/suggest_policy.h
@@ -0,0 +1,39 @@
+/*
+ * 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/native/jni/src/suggest/core/policy/traversal.h b/native/jni/src/suggest/core/policy/traversal.h
new file mode 100644
index 0000000..02c358a
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/traversal.h
@@ -0,0 +1,64 @@
+/*
+ * 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 = 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 allowPartialCommit() const = 0;
+    virtual int getDefaultExpandDicNodeSize() const = 0;
+    virtual int getMaxCacheSize() 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/native/jni/src/suggest/core/policy/weighting.cpp b/native/jni/src/suggest/core/policy/weighting.cpp
new file mode 100644
index 0000000..e62b704
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/weighting.cpp
@@ -0,0 +1,245 @@
+/*
+ * 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 "char_utils.h"
+#include "defines.h"
+#include "hash_map_compat.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 {
+
+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:
+        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_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,
+        hash_map_compat<int, int16_t> *const bigramCacheMap) {
+    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, bigramCacheMap);
+    const bool edit = Weighting::isEditCorrection(correctionType);
+    const bool proximity = Weighting::isProximityCorrection(weighting, correctionType,
+            traverseSession, 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, edit, proximity);
+}
+
+/* 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:
+        return weighting->getNewWordCost(dicNode);
+    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_SPACE_SUBSTITUTION:
+        return weighting->getSpaceSubstitutionCost();
+    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,
+        hash_map_compat<int, int16_t> *const bigramCacheMap) {
+    switch(correctionType) {
+    case CT_OMISSION:
+        return 0.0f;
+    case CT_SUBSTITUTION:
+        return 0.0f;
+    case CT_NEW_WORD:
+        return weighting->getNewWordBigramCost(traverseSession, parentDicNode, bigramCacheMap);
+    case CT_MATCH:
+        return 0.0f;
+    case CT_COMPLETION:
+        return 0.0f;
+    case CT_TERMINAL: {
+        const float languageImprobability =
+                DicNodeUtils::getBigramNodeImprobability(
+                        traverseSession->getOffsetDict(), dicNode, bigramCacheMap);
+        return weighting->getTerminalLanguageCost(traverseSession, dicNode, languageImprobability);
+    }
+    case CT_SPACE_SUBSTITUTION:
+        return 0.0f;
+    case CT_INSERTION:
+        return 0.0f;
+    case CT_TRANSPOSITION:
+        return 0.0f;
+    default:
+        return 0.0f;
+    }
+}
+
+/* static */ bool Weighting::isEditCorrection(const CorrectionType correctionType) {
+    switch(correctionType) {
+        case CT_OMISSION:
+            return true;
+        case CT_ADDITIONAL_PROXIMITY:
+            // Should return true?
+            return false;
+        case CT_SUBSTITUTION:
+            // Should return true?
+            return false;
+        case CT_NEW_WORD:
+            return false;
+        case CT_MATCH:
+            return false;
+        case CT_COMPLETION:
+            return false;
+        case CT_TERMINAL:
+            return false;
+        case CT_SPACE_SUBSTITUTION:
+            return false;
+        case CT_INSERTION:
+            return true;
+        case CT_TRANSPOSITION:
+            return true;
+        default:
+            return false;
+    }
+}
+
+/* static */ bool Weighting::isProximityCorrection(const Weighting *const weighting,
+        const CorrectionType correctionType,
+        const DicTraverseSession *const traverseSession, const DicNode *const dicNode) {
+    switch(correctionType) {
+        case CT_OMISSION:
+            return false;
+        case CT_ADDITIONAL_PROXIMITY:
+            return false;
+        case CT_SUBSTITUTION:
+            return false;
+        case CT_NEW_WORD:
+            return false;
+        case CT_MATCH:
+            return weighting->isProximityDicNode(traverseSession, dicNode);
+        case CT_COMPLETION:
+            return false;
+        case CT_TERMINAL:
+            return false;
+        case CT_SPACE_SUBSTITUTION:
+            return false;
+        case CT_INSERTION:
+            return false;
+        case CT_TRANSPOSITION:
+            return false;
+        default:
+            return false;
+    }
+}
+
+/* static */ int Weighting::getForwardInputCount(const CorrectionType correctionType) {
+    switch(correctionType) {
+        case CT_OMISSION:
+            return 0;
+        case CT_ADDITIONAL_PROXIMITY:
+            return 0;
+        case CT_SUBSTITUTION:
+            return 0;
+        case CT_NEW_WORD:
+            return 0;
+        case CT_MATCH:
+            return 1;
+        case CT_COMPLETION:
+            return 0;
+        case CT_TERMINAL:
+            return 0;
+        case CT_SPACE_SUBSTITUTION:
+            return 1;
+        case CT_INSERTION:
+            return 2;
+        case CT_TRANSPOSITION:
+            return 2;
+        default:
+            return 0;
+    }
+}
+}  // namespace latinime
diff --git a/native/jni/src/suggest/core/policy/weighting.h b/native/jni/src/suggest/core/policy/weighting.h
new file mode 100644
index 0000000..b92dbe2
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/weighting.h
@@ -0,0 +1,105 @@
+/*
+ * 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"
+#include "hash_map_compat.h"
+
+namespace latinime {
+
+class DicNode;
+class DicTraverseSession;
+struct DicNode_InputStateG;
+
+class Weighting {
+ public:
+    static void addCostAndForwardInputIndex(const Weighting *const weighting,
+            const CorrectionType correctionType,
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const parentDicNode, DicNode *const dicNode,
+            hash_map_compat<int, int16_t> *const bigramCacheMap);
+
+ 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 getNewWordCost(const DicNode *const dicNode) const = 0;
+
+    virtual float getNewWordBigramCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
+            hash_map_compat<int, int16_t> *const bigramCacheMap) const = 0;
+
+    virtual float getCompletionCost(
+            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 = 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,
+            hash_map_compat<int, int16_t> *const bigramCacheMap);
+    // TODO: Move to TypingWeighting and GestureWeighting?
+    static bool isEditCorrection(const CorrectionType correctionType);
+    // TODO: Move to TypingWeighting and GestureWeighting?
+    static bool isProximityCorrection(const Weighting *const weighting,
+            const CorrectionType correctionType, const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode);
+    // TODO: Move to TypingWeighting and GestureWeighting?
+    static int getForwardInputCount(const CorrectionType correctionType);
+};
+} // namespace latinime
+#endif // LATINIME_WEIGHTING_H
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
new file mode 100644
index 0000000..5b783a2
--- /dev/null
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -0,0 +1,111 @@
+/*
+ * 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 "dictionary.h"
+#include "dic_traverse_wrapper.h"
+#include "jni.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+
+namespace latinime {
+
+const int DicTraverseSession::CACHE_START_INPUT_LENGTH_THRESHOLD = 20;
+
+// A factory method for DicTraverseSession
+static void *getSessionInstance(JNIEnv *env, jstring localeStr) {
+    return new DicTraverseSession(env, localeStr);
+}
+
+// TODO: Pass "DicTraverseSession *traverseSession" when the source code structure settles down.
+static void initSessionInstance(void *traverseSession, const Dictionary *const dictionary,
+        const int *prevWord, const int prevWordLength) {
+    if (traverseSession) {
+        DicTraverseSession *tSession = static_cast<DicTraverseSession *>(traverseSession);
+        tSession->init(dictionary, prevWord, prevWordLength);
+    }
+}
+
+// TODO: Pass "DicTraverseSession *traverseSession" when the source code structure settles down.
+static void releaseSessionInstance(void *traverseSession) {
+    delete static_cast<DicTraverseSession *>(traverseSession);
+}
+
+// An ad-hoc internal class to register the factory method defined above
+class TraverseSessionFactoryRegisterer {
+ public:
+    TraverseSessionFactoryRegisterer() {
+        DicTraverseWrapper::setTraverseSessionFactoryMethod(getSessionInstance);
+        DicTraverseWrapper::setTraverseSessionInitMethod(initSessionInstance);
+        DicTraverseWrapper::setTraverseSessionReleaseMethod(releaseSessionInstance);
+    }
+ private:
+    DISALLOW_COPY_AND_ASSIGN(TraverseSessionFactoryRegisterer);
+};
+
+// To invoke the TraverseSessionFactoryRegisterer constructor in the global constructor.
+static TraverseSessionFactoryRegisterer traverseSessionFactoryRegisterer;
+
+void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord,
+        int prevWordLength) {
+    mDictionary = dictionary;
+    if (!prevWord) {
+        mPrevWordPos = NOT_VALID_WORD;
+        return;
+    }
+    mPrevWordPos = DicNodeUtils::getWordPos(dictionary->getOffsetDict(), prevWord, prevWordLength);
+}
+
+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 uint8_t *DicTraverseSession::getOffsetDict() const {
+    return mDictionary->getOffsetDict();
+}
+
+int DicTraverseSession::getDictFlags() const {
+    return mDictionary->getDictFlags();
+}
+
+void DicTraverseSession::resetCache(const int nextActiveCacheSize, const int maxWords) {
+    mDicNodesCache.reset(nextActiveCacheSize, maxWords);
+    mBigramCacheMap.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/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
new file mode 100644
index 0000000..525d198
--- /dev/null
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -0,0 +1,171 @@
+/*
+ * 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 "hash_map_compat.h"
+#include "jni.h"
+#include "proximity_info_state.h"
+#include "suggest/core/dicnode/dic_nodes_cache.h"
+
+namespace latinime {
+
+class Dictionary;
+class ProximityInfo;
+
+class DicTraverseSession {
+ public:
+    AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr)
+            : mPrevWordPos(NOT_VALID_WORD), mProximityInfo(0),
+              mDictionary(0), mDicNodesCache(), mBigramCacheMap(),
+              mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1) {
+        // 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);
+    // 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 nextActiveCacheSize, const int maxWords);
+
+    const uint8_t *getOffsetDict() const;
+    int getDictFlags() const;
+
+    //--------------------
+    // getters and setters
+    //--------------------
+    const ProximityInfo *getProximityInfo() const { return mProximityInfo; }
+    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; }
+    hash_map_compat<int, int16_t> *getBigramCacheMap() { return &mBigramCacheMap; }
+    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;
+        }
+        *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;
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseSession);
+    // threshold to start caching
+    static const int CACHE_START_INPUT_LENGTH_THRESHOLD;
+    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;
+
+    DicNodesCache mDicNodesCache;
+    // Temporary cache for bigram frequencies
+    hash_map_compat<int, int16_t> mBigramCacheMap;
+    ProximityInfoState mProximityInfoStates[MAX_POINTER_COUNT_G];
+
+    int mInputSize;
+    bool mPartiallyCommited;
+    int mMaxPointerCount;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_TRAVERSE_SESSION_H
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
new file mode 100644
index 0000000..67d351f
--- /dev/null
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -0,0 +1,543 @@
+/*
+ * 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 "char_utils.h"
+#include "dictionary.h"
+#include "digraph_utils.h"
+#include "proximity_info.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/shortcut_utils.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"
+#include "terminal_attributes.h"
+
+namespace latinime {
+
+// Initialization of class constants.
+const int Suggest::LOOKAHEAD_DIC_NODES_CACHE_SIZE = 25;
+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;
+const float Suggest::AUTOCORRECT_LANGUAGE_FEATURE_THRESHOLD = 0.6f;
+
+const bool Suggest::CORRECT_SPACE_OMISSION = true;
+const bool Suggest::CORRECT_TRANSPOSITION = true;
+const bool Suggest::CORRECT_INSERTION = true;
+const bool Suggest::CORRECT_OMISSION_G = true;
+
+/**
+ * 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) 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);
+    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;
+    }
+    if (TRAVERSAL->allowPartialCommit()) {
+        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(), MAX_RESULTS);
+        // Create a new dic node here
+        DicNode rootNode;
+        DicNodeUtils::initAsRoot(traverseSession->getDicRootPos(),
+                traverseSession->getOffsetDict(), 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 *spaceIndices, int *outputTypes) 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) {
+        ++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;
+    // 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 TerminalAttributes terminalAttributes(traverseSession->getOffsetDict(),
+                terminalDicNode->getFlags(), terminalDicNode->getAttributesPos());
+        const int originalTerminalProbability = terminalDicNode->getProbability();
+
+        // Do not suggest words with a 0 probability, or entries that are blacklisted or do not
+        // represent a word. However, we should still submit their shortcuts if any.
+        const bool isValidWord =
+                originalTerminalProbability > 0 && !terminalAttributes.isBlacklistedOrNotAWord();
+        // Increase output score of top typing suggestion to ensure autocorrection.
+        // TODO: Better integration with java side autocorrection logic.
+        // Force autocorrection for obvious long multi-word suggestions.
+        const bool isForceCommitMultiWords = TRAVERSAL->allowPartialCommit()
+                && (traverseSession->isPartiallyCommited()
+                        || (traverseSession->getInputSize() >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT
+                                && terminalDicNode->hasMultipleWords()));
+
+        const int finalScore = SCORING->calculateFinalScore(
+                compoundDistance, traverseSession->getInputSize(),
+                isForceCommitMultiWords || (isValidWord && SCORING->doesAutoCorrectValidWord()));
+
+        maxScore = max(maxScore, finalScore);
+
+        if (TRAVERSAL->allowPartialCommit()) {
+            // Index for top typing suggestion should be 0.
+            if (isValidWord && outputWordIndex == 0) {
+                terminalDicNode->outputSpacePositionsResult(spaceIndices);
+            }
+        }
+
+        // Do not suggest words with a 0 probability, or entries that are blacklisted or do not
+        // represent a word. However, we should still submit their shortcuts if any.
+        if (isValidWord) {
+            outputTypes[outputWordIndex] = Dictionary::KIND_CORRECTION;
+            frequencies[outputWordIndex] = finalScore;
+            // Populate the outputChars array with the suggested word.
+            const int startIndex = outputWordIndex * MAX_WORD_LENGTH;
+            terminalDicNode->outputResult(&outputCodePoints[startIndex]);
+            ++outputWordIndex;
+        }
+
+        const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);
+        outputWordIndex = ShortcutUtils::outputShortcuts(&terminalAttributes, outputWordIndex,
+                finalScore, outputCodePoints, frequencies, outputTypes, sameAsTyped);
+        DicNode::managedDelete(terminalDicNode);
+    }
+
+    if (hasMostProbableString) {
+        SCORING->safetyNetForMostProbableString(terminalSize, maxScore,
+                &outputCodePoints[0], &frequencies[0]);
+    }
+    return outputWordIndex;
+}
+
+/**
+ * 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.
+            if (CORRECT_TRANSPOSITION) {
+                processDicNodeAsTransposition(traverseSession, &dicNode);
+            }
+            if (CORRECT_INSERTION) {
+                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->getOffsetDict(), &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->getDictFlags(),
+                        childDicNode->getNodeCodePoint())) {
+                    correctionDicNode.initByCopy(childDicNode);
+                    correctionDicNode.advanceDigraphIndex();
+                    processDicNodeAsDigraph(traverseSession, &correctionDicNode);
+                }
+                if (allowsErrorCorrections
+                        && TRAVERSAL->isOmission(traverseSession, &dicNode, childDicNode)) {
+                    // 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 (TRAVERSAL->needsToTraverseAllUserInput()
+            && dicNode->getInputIndex(0) < traverseSession->getInputSize()) {
+        return;
+    }
+
+    if (dicNode->shouldBeFilterdBySafetyNetForBigram()) {
+        return;
+    }
+    // Create a non-cached node here.
+    DicNode terminalDicNode;
+    DicNodeUtils::initByCopy(dicNode, &terminalDicNode);
+    Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TERMINAL, traverseSession, 0,
+            &terminalDicNode, traverseSession->getBigramCacheMap());
+    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 {
+    Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_ADDITIONAL_PROXIMITY,
+            traverseSession, dicNode, childDicNode, 0 /* bigramCacheMap */);
+    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 /* bigramCacheMap */);
+    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 {
+    // If the omission is surely intentional that it should incur zero cost.
+    const bool isZeroCostOmission = dicNode->isZeroCostOmission();
+    DicNodeVector childDicNodes;
+
+    DicNodeUtils::getAllChildDicNodes(dicNode, traverseSession->getOffsetDict(), &childDicNodes);
+
+    const int size = childDicNodes.getSizeAndLock();
+    for (int i = 0; i < size; i++) {
+        DicNode *const childDicNode = childDicNodes[i];
+        if (!isZeroCostOmission) {
+            // Treat this word as omission
+            Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_OMISSION, traverseSession,
+                    dicNode, childDicNode, 0 /* bigramCacheMap */);
+        }
+        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::getProximityChildDicNodes(dicNode, traverseSession->getOffsetDict(),
+            traverseSession->getProximityInfoState(0), pointIndex + 1, true, &childDicNodes);
+    const int size = childDicNodes.getSizeAndLock();
+    for (int i = 0; i < size; i++) {
+        DicNode *const childDicNode = childDicNodes[i];
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_INSERTION, traverseSession,
+                dicNode, childDicNode, 0 /* bigramCacheMap */);
+        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::getProximityChildDicNodes(dicNode, traverseSession->getOffsetDict(),
+            traverseSession->getProximityInfoState(0), pointIndex + 1, false, &childDicNodes1);
+    const int childSize1 = childDicNodes1.getSizeAndLock();
+    for (int i = 0; i < childSize1; i++) {
+        if (childDicNodes1[i]->hasChildren()) {
+            DicNodeVector childDicNodes2;
+            DicNodeUtils::getProximityChildDicNodes(
+                    childDicNodes1[i], traverseSession->getOffsetDict(),
+                    traverseSession->getProximityInfoState(0), pointIndex, false, &childDicNodes2);
+            const int childSize2 = childDicNodes2.getSizeAndLock();
+            for (int j = 0; j < childSize2; j++) {
+                DicNode *const childDicNode2 = childDicNodes2[j];
+                Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TRANSPOSITION,
+                        traverseSession, childDicNodes1[i], childDicNode2, 0 /* bigramCacheMap */);
+                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 /* bigramCacheMap */);
+    } else { // completion
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_MATCH, traverseSession,
+                0 /* parentDicNode */, dicNode, 0 /* bigramCacheMap */);
+    }
+}
+
+/**
+ * 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->getDicRootPos(),
+            traverseSession->getOffsetDict(), dicNode, &newDicNode);
+    Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_NEW_WORD, traverseSession, dicNode,
+            &newDicNode, traverseSession->getBigramCacheMap());
+    if (spaceSubstitution) {
+        // Merge this with CT_NEW_WORD
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_SPACE_SUBSTITUTION,
+                traverseSession, 0, &newDicNode, 0 /* bigramCacheMap */);
+    }
+    traverseSession->getDicTraverseCache()->copyPushNextActive(&newDicNode);
+}
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/suggest.h b/native/jni/src/suggest/core/suggest.h
new file mode 100644
index 0000000..becd6c1
--- /dev/null
+++ b/native/jni/src/suggest/core/suggest.h
@@ -0,0 +1,106 @@
+/*
+ * 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) 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 *outputIndices, int *outputTypes) 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;
+
+    // Dic nodes cache size for lookahead (autocompletion)
+    static const int LOOKAHEAD_DIC_NODES_CACHE_SIZE;
+    // Max characters to lookahead
+    static const int MAX_LOOKAHEAD;
+    // 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;
+    // Base value for converting costs into scores (low so will not autocorrect without classifier)
+    static const float BASE_OUTPUT_SCORE;
+
+    // Threshold for autocorrection classifier
+    static const float AUTOCORRECT_CLASSIFICATION_THRESHOLD;
+    // Threshold for computing the language model feature for autocorrect classification
+    static const float AUTOCORRECT_LANGUAGE_FEATURE_THRESHOLD;
+
+    // Typing error correction settings
+    static const bool CORRECT_SPACE_OMISSION;
+    static const bool CORRECT_TRANSPOSITION;
+    static const bool CORRECT_INSERTION;
+
+    const Traversal *const TRAVERSAL;
+    const Scoring *const SCORING;
+    const Weighting *const WEIGHTING;
+
+    static const bool CORRECT_OMISSION_G;
+};
+} // namespace latinime
+#endif // LATINIME_SUGGEST_IMPL_H
diff --git a/native/jni/src/suggest/suggest_interface.h b/native/jni/src/suggest/core/suggest_interface.h
similarity index 100%
rename from native/jni/src/suggest/suggest_interface.h
rename to native/jni/src/suggest/core/suggest_interface.h
diff --git a/native/jni/src/suggest/gesture_suggest.cpp b/native/jni/src/suggest/gesture_suggest.cpp
deleted file mode 100644
index fce5621..0000000
--- a/native/jni/src/suggest/gesture_suggest.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "gesture_suggest.h"
-
-namespace latinime {
-    SuggestInterface *(*GestureSuggest::sGestureSuggestFactoryMethod)() = 0;
-
-    GestureSuggest::~GestureSuggest() {
-        delete mSuggestInterface;
-    }
-} // namespace latinime
diff --git a/native/jni/src/suggest/gesture_suggest.h b/native/jni/src/suggest/gesture_suggest.h
deleted file mode 100644
index 82c3a69..0000000
--- a/native/jni/src/suggest/gesture_suggest.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_GESTURE_SUGGEST_H
-#define LATINIME_GESTURE_SUGGEST_H
-
-#include "defines.h"
-#include "suggest_interface.h"
-
-namespace latinime {
-
-class ProximityInfo;
-
-class GestureSuggest : public SuggestInterface {
- public:
-    GestureSuggest() : mSuggestInterface(getGestureSuggestInstance()) {}
-
-    virtual ~GestureSuggest();
-
-    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) const {
-        if (!mSuggestInterface) {
-            return 0;
-        }
-        return mSuggestInterface->getSuggestions(pInfo, traverseSession, inputXs, inputYs, times,
-                pointerIds, inputCodePoints, inputSize, commitPoint, outWords, frequencies,
-                outputIndices, outputTypes);
-    }
-
-    static void setGestureSuggestFactoryMethod(SuggestInterface *(*factoryMethod)()) {
-        sGestureSuggestFactoryMethod = factoryMethod;
-    }
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(GestureSuggest);
-    static SuggestInterface *getGestureSuggestInstance() {
-        if (!sGestureSuggestFactoryMethod) {
-            return 0;
-        }
-        return sGestureSuggestFactoryMethod();
-    }
-
-    static SuggestInterface *(*sGestureSuggestFactoryMethod)();
-    SuggestInterface *mSuggestInterface;
-};
-} // namespace latinime
-#endif // LATINIME_GESTURE_SUGGEST_H
diff --git a/native/jni/src/suggest/typing_suggest.cpp b/native/jni/src/suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp
similarity index 77%
rename from native/jni/src/suggest/typing_suggest.cpp
rename to native/jni/src/suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp
index 56bd5b6..6d31739 100644
--- a/native/jni/src/suggest/typing_suggest.cpp
+++ b/native/jni/src/suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-#include "typing_suggest.h"
+#include "gesture_suggest_policy_factory.h"
 
 namespace latinime {
-    SuggestInterface *(*TypingSuggest::sTypingSuggestFactoryMethod)() = 0;
-
-    TypingSuggest::~TypingSuggest() {
-        delete mSuggestInterface;
-    }
+    const SuggestPolicy *(*GestureSuggestPolicyFactory::sGestureSuggestFactoryMethod)() = 0;
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/gesture/gesture_suggest_policy_factory.h b/native/jni/src/suggest/policyimpl/gesture/gesture_suggest_policy_factory.h
new file mode 100644
index 0000000..509b01f
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/gesture/gesture_suggest_policy_factory.h
@@ -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.
+ */
+
+#ifndef LATINIME_GESTURE_SUGGEST_POLICY_FACTORY_H
+#define LATINIME_GESTURE_SUGGEST_POLICY_FACTORY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class SuggestPolicy;
+
+class GestureSuggestPolicyFactory {
+ public:
+    static void setGestureSuggestPolicyFactoryMethod(const SuggestPolicy *(*factoryMethod)()) {
+        sGestureSuggestFactoryMethod = factoryMethod;
+    }
+
+    static const SuggestPolicy *getGestureSuggestPolicy() {
+        if (!sGestureSuggestFactoryMethod) {
+            return 0;
+        }
+        return sGestureSuggestFactoryMethod();
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(GestureSuggestPolicyFactory);
+    static const SuggestPolicy *(*sGestureSuggestFactoryMethod)();
+};
+} // namespace latinime
+#endif // LATINIME_GESTURE_SUGGEST_POLICY_FACTORY_H
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
new file mode 100644
index 0000000..0fa684f
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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/policyimpl/typing/scoring_params.h"
+
+namespace latinime {
+// TODO: RENAME all
+const float ScoringParams::MAX_SPATIAL_DISTANCE = 1.0f;
+const int ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY = 40;
+const int ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY_FOR_CAPPED = 120;
+const float ScoringParams::AUTOCORRECT_OUTPUT_THRESHOLD = 1.0f;
+const int ScoringParams::MAX_CACHE_DIC_NODE_SIZE = 125;
+const int ScoringParams::THRESHOLD_SHORT_WORD_LENGTH = 4;
+
+const float ScoringParams::DISTANCE_WEIGHT_LENGTH = 0.132f;
+const float ScoringParams::PROXIMITY_COST = 0.086f;
+const float ScoringParams::FIRST_PROXIMITY_COST = 0.104f;
+const float ScoringParams::OMISSION_COST = 0.388f;
+const float ScoringParams::OMISSION_COST_SAME_CHAR = 0.431f;
+const float ScoringParams::OMISSION_COST_FIRST_CHAR = 0.532f;
+const float ScoringParams::INSERTION_COST = 0.670f;
+const float ScoringParams::INSERTION_COST_SAME_CHAR = 0.526f;
+const float ScoringParams::INSERTION_COST_FIRST_CHAR = 0.563f;
+const float ScoringParams::TRANSPOSITION_COST = 0.494f;
+const float ScoringParams::SPACE_SUBSTITUTION_COST = 0.239f;
+const float ScoringParams::ADDITIONAL_PROXIMITY_COST = 0.380f;
+const float ScoringParams::SUBSTITUTION_COST = 0.363f;
+const float ScoringParams::COST_NEW_WORD = 0.054f;
+const float ScoringParams::COST_NEW_WORD_CAPITALIZED = 0.174f;
+const float ScoringParams::DISTANCE_WEIGHT_LANGUAGE = 1.123f;
+const float ScoringParams::COST_FIRST_LOOKAHEAD = 0.462f;
+const float ScoringParams::COST_LOOKAHEAD = 0.092f;
+const float ScoringParams::HAS_PROXIMITY_TERMINAL_COST = 0.126f;
+const float ScoringParams::HAS_EDIT_CORRECTION_TERMINAL_COST = 0.056f;
+const float ScoringParams::HAS_MULTI_WORD_TERMINAL_COST = 0.136f;
+const float ScoringParams::TYPING_BASE_OUTPUT_SCORE = 1.0f;
+const float ScoringParams::TYPING_MAX_OUTPUT_SCORE_PER_INPUT = 0.1f;
+const float ScoringParams::MAX_NORM_DISTANCE_FOR_EDIT = 0.1f;
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.h b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
new file mode 100644
index 0000000..8f104b3
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_SCORING_PARAMS_H
+#define LATINIME_SCORING_PARAMS_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class ScoringParams {
+ public:
+    // Fixed model parameters
+    static const float MAX_SPATIAL_DISTANCE;
+    static const int THRESHOLD_NEXT_WORD_PROBABILITY;
+    static const int THRESHOLD_NEXT_WORD_PROBABILITY_FOR_CAPPED;
+    static const float AUTOCORRECT_OUTPUT_THRESHOLD;
+    static const int MAX_CACHE_DIC_NODE_SIZE;
+    static const int THRESHOLD_SHORT_WORD_LENGTH;
+
+    // Numerically optimized parameters (currently for tap typing only).
+    // TODO: add ability to modify these constants programmatically.
+    // TODO: explore optimization of gesture parameters.
+    static const float DISTANCE_WEIGHT_LENGTH;
+    static const float PROXIMITY_COST;
+    static const float FIRST_PROXIMITY_COST;
+    static const float OMISSION_COST;
+    static const float OMISSION_COST_SAME_CHAR;
+    static const float OMISSION_COST_FIRST_CHAR;
+    static const float INSERTION_COST;
+    static const float INSERTION_COST_SAME_CHAR;
+    static const float INSERTION_COST_FIRST_CHAR;
+    static const float TRANSPOSITION_COST;
+    static const float SPACE_SUBSTITUTION_COST;
+    static const float ADDITIONAL_PROXIMITY_COST;
+    static const float SUBSTITUTION_COST;
+    static const float COST_NEW_WORD;
+    static const float COST_NEW_WORD_CAPITALIZED;
+    static const float DISTANCE_WEIGHT_LANGUAGE;
+    static const float COST_FIRST_LOOKAHEAD;
+    static const float COST_LOOKAHEAD;
+    static const float HAS_PROXIMITY_TERMINAL_COST;
+    static const float HAS_EDIT_CORRECTION_TERMINAL_COST;
+    static const float HAS_MULTI_WORD_TERMINAL_COST;
+    static const float TYPING_BASE_OUTPUT_SCORE;
+    static const float TYPING_MAX_OUTPUT_SCORE_PER_INPUT;
+    static const float MAX_NORM_DISTANCE_FOR_EDIT;
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ScoringParams);
+};
+} // namespace latinime
+#endif // LATINIME_SCORING_PARAMS_H
diff --git a/native/jni/src/suggest/typing_suggest.cpp b/native/jni/src/suggest/policyimpl/typing/typing_scoring.cpp
similarity index 68%
copy from native/jni/src/suggest/typing_suggest.cpp
copy to native/jni/src/suggest/policyimpl/typing/typing_scoring.cpp
index 56bd5b6..d8c6175 100644
--- a/native/jni/src/suggest/typing_suggest.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/typing_scoring.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-#include "typing_suggest.h"
+#include "suggest/policyimpl/typing/typing_scoring.h"
 
 namespace latinime {
-    SuggestInterface *(*TypingSuggest::sTypingSuggestFactoryMethod)() = 0;
-
-    TypingSuggest::~TypingSuggest() {
-        delete mSuggestInterface;
-    }
-} // namespace latinime
+const TypingScoring TypingScoring::sInstance;
+}  // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
new file mode 100644
index 0000000..90e2133
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
@@ -0,0 +1,82 @@
+/*
+ * 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_TYPING_SCORING_H
+#define LATINIME_TYPING_SCORING_H
+
+#include "defines.h"
+#include "suggest/core/policy/scoring.h"
+#include "suggest/policyimpl/typing/scoring_params.h"
+
+namespace latinime {
+
+class DicNode;
+class DicTraverseSession;
+
+class TypingScoring : public Scoring {
+ public:
+    static const TypingScoring *getInstance() { return &sInstance; }
+
+    AK_FORCE_INLINE bool getMostProbableString(
+            const DicTraverseSession *const traverseSession, const int terminalSize,
+            const float languageWeight, int *const outputCodePoints, int *const type,
+            int *const freq) const {
+        return false;
+    }
+
+    AK_FORCE_INLINE void safetyNetForMostProbableString(const int terminalSize,
+            const int maxScore, int *const outputCodePoints, int *const frequencies) const {
+    }
+
+    AK_FORCE_INLINE void searchWordWithDoubleLetter(DicNode *terminals,
+            const int terminalSize, int *doubleLetterTerminalIndex,
+            DoubleLetterLevel *doubleLetterLevel) const {
+    }
+
+    AK_FORCE_INLINE float getAdjustedLanguageWeight(DicTraverseSession *const traverseSession,
+             DicNode *const terminals, const int size) const {
+        return 1.0f;
+    }
+
+    AK_FORCE_INLINE int calculateFinalScore(const float compoundDistance,
+            const int inputSize, const bool forceCommit) const {
+        const float maxDistance = ScoringParams::DISTANCE_WEIGHT_LANGUAGE
+                + static_cast<float>(inputSize) * ScoringParams::TYPING_MAX_OUTPUT_SCORE_PER_INPUT;
+        return static_cast<int>((ScoringParams::TYPING_BASE_OUTPUT_SCORE
+                - (compoundDistance / maxDistance)
+                + (forceCommit ? ScoringParams::AUTOCORRECT_OUTPUT_THRESHOLD : 0.0f))
+                        * SUGGEST_INTERFACE_OUTPUT_SCALE);
+    }
+
+    AK_FORCE_INLINE float getDoubleLetterDemotionDistanceCost(const int terminalIndex,
+            const int doubleLetterTerminalIndex,
+            const DoubleLetterLevel doubleLetterLevel) const {
+        return 0.0f;
+    }
+
+    AK_FORCE_INLINE bool doesAutoCorrectValidWord() const {
+        return false;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(TypingScoring);
+    static const TypingScoring sInstance;
+
+    TypingScoring() {}
+    ~TypingScoring() {}
+};
+} // namespace latinime
+#endif // LATINIME_TYPING_SCORING_H
diff --git a/native/jni/src/suggest/typing_suggest.cpp b/native/jni/src/suggest/policyimpl/typing/typing_suggest_policy.cpp
similarity index 71%
copy from native/jni/src/suggest/typing_suggest.cpp
copy to native/jni/src/suggest/policyimpl/typing/typing_suggest_policy.cpp
index 56bd5b6..0c27639 100644
--- a/native/jni/src/suggest/typing_suggest.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/typing_suggest_policy.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,12 +14,8 @@
  * limitations under the License.
  */
 
-#include "typing_suggest.h"
+#include "suggest/policyimpl/typing/typing_suggest_policy.h"
 
 namespace latinime {
-    SuggestInterface *(*TypingSuggest::sTypingSuggestFactoryMethod)() = 0;
-
-    TypingSuggest::~TypingSuggest() {
-        delete mSuggestInterface;
-    }
+const TypingSuggestPolicy TypingSuggestPolicy::sInstance;
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_suggest_policy.h b/native/jni/src/suggest/policyimpl/typing/typing_suggest_policy.h
new file mode 100644
index 0000000..35f4809
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/typing/typing_suggest_policy.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_TYPING_SUGGEST_POLICY_H
+#define LATINIME_TYPING_SUGGEST_POLICY_H
+
+#include "defines.h"
+#include "suggest/core/policy/suggest_policy.h"
+#include "suggest/policyimpl/typing/typing_scoring.h"
+#include "suggest/policyimpl/typing/typing_traversal.h"
+#include "suggest/policyimpl/typing/typing_weighting.h"
+
+namespace latinime {
+
+class Scoring;
+class Traversal;
+class Weighting;
+
+class TypingSuggestPolicy : public SuggestPolicy {
+ public:
+    static const TypingSuggestPolicy *getInstance() { return &sInstance; }
+
+    TypingSuggestPolicy() {}
+    virtual ~TypingSuggestPolicy() {}
+    AK_FORCE_INLINE const Traversal *getTraversal() const {
+        return TypingTraversal::getInstance();
+    }
+
+    AK_FORCE_INLINE const Scoring *getScoring() const {
+        return TypingScoring::getInstance();
+    }
+
+    AK_FORCE_INLINE const Weighting *getWeighting() const {
+        return TypingWeighting::getInstance();
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(TypingSuggestPolicy);
+    static const TypingSuggestPolicy sInstance;
+};
+} // namespace latinime
+#endif // LATINIME_TYPING_SUGGEST_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_suggest_policy_factory.h b/native/jni/src/suggest/policyimpl/typing/typing_suggest_policy_factory.h
new file mode 100644
index 0000000..a67b45b
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/typing/typing_suggest_policy_factory.h
@@ -0,0 +1,37 @@
+/*
+ * 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_TYPING_SUGGEST_POLICY_FACTORY_H
+#define LATINIME_TYPING_SUGGEST_POLICY_FACTORY_H
+
+#include "defines.h"
+#include "typing_suggest_policy.h"
+
+namespace latinime {
+
+class SuggestPolicy;
+
+class TypingSuggestPolicyFactory {
+ public:
+    static const SuggestPolicy *getTypingSuggestPolicy() {
+        return TypingSuggestPolicy::getInstance();
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(TypingSuggestPolicyFactory);
+};
+} // namespace latinime
+#endif // LATINIME_TYPING_SUGGEST_POLICY_FACTORY_H
diff --git a/native/jni/src/suggest/typing_suggest.cpp b/native/jni/src/suggest/policyimpl/typing/typing_traversal.cpp
similarity index 61%
copy from native/jni/src/suggest/typing_suggest.cpp
copy to native/jni/src/suggest/policyimpl/typing/typing_traversal.cpp
index 56bd5b6..66f8ba9 100644
--- a/native/jni/src/suggest/typing_suggest.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-#include "typing_suggest.h"
+#include "suggest/policyimpl/typing/typing_traversal.h"
 
 namespace latinime {
-    SuggestInterface *(*TypingSuggest::sTypingSuggestFactoryMethod)() = 0;
-
-    TypingSuggest::~TypingSuggest() {
-        delete mSuggestInterface;
-    }
-} // namespace latinime
+const bool TypingTraversal::CORRECT_OMISSION = true;
+const bool TypingTraversal::CORRECT_SPACE_SUBSTITUTION = true;
+const bool TypingTraversal::CORRECT_SPACE_OMISSION = true;
+const TypingTraversal TypingTraversal::sInstance;
+}  // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
new file mode 100644
index 0000000..f22029a
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -0,0 +1,184 @@
+/*
+ * 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_TYPING_TRAVERSAL_H
+#define LATINIME_TYPING_TRAVERSAL_H
+
+#include <stdint.h>
+
+#include "char_utils.h"
+#include "defines.h"
+#include "proximity_info_state.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/policy/traversal.h"
+#include "suggest/core/session/dic_traverse_session.h"
+#include "suggest/policyimpl/typing/scoring_params.h"
+
+namespace latinime {
+class TypingTraversal : public Traversal {
+ public:
+    static const TypingTraversal *getInstance() { return &sInstance; }
+
+    AK_FORCE_INLINE int getMaxPointerCount() const {
+        return MAX_POINTER_COUNT;
+    }
+
+    AK_FORCE_INLINE bool allowsErrorCorrections(const DicNode *const dicNode) const {
+        return dicNode->getNormalizedSpatialDistance()
+                < ScoringParams::MAX_NORM_DISTANCE_FOR_EDIT;
+    }
+
+    AK_FORCE_INLINE bool isOmission(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode, const DicNode *const childDicNode) const {
+        if (!CORRECT_OMISSION) {
+            return false;
+        }
+        const int inputSize = traverseSession->getInputSize();
+        // TODO: Don't refer to isCompletion?
+        if (dicNode->isCompletion(inputSize)) {
+            return false;
+        }
+        if (dicNode->canBeIntentionalOmission()) {
+            return true;
+        }
+        const int point0Index = dicNode->getInputIndex(0);
+        const int currentBaseLowerCodePoint =
+                toBaseLowerCase(childDicNode->getNodeCodePoint());
+        const int typedBaseLowerCodePoint =
+                toBaseLowerCase(traverseSession->getProximityInfoState(0)
+                        ->getPrimaryCodePointAt(point0Index));
+        return (currentBaseLowerCodePoint != typedBaseLowerCodePoint);
+    }
+
+    AK_FORCE_INLINE bool isSpaceSubstitutionTerminal(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
+        if (!CORRECT_SPACE_SUBSTITUTION) {
+            return false;
+        }
+        if (!canDoLookAheadCorrection(traverseSession, dicNode)) {
+            return false;
+        }
+        const int point0Index = dicNode->getInputIndex(0);
+        return dicNode->isTerminalWordNode()
+                && traverseSession->getProximityInfoState(0)->
+                        hasSpaceProximity(point0Index);
+    }
+
+    AK_FORCE_INLINE bool isSpaceOmissionTerminal(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
+        if (!CORRECT_SPACE_OMISSION) {
+            return false;
+        }
+        const int inputSize = traverseSession->getInputSize();
+        // TODO: Don't refer to isCompletion?
+        if (dicNode->isCompletion(inputSize)) {
+            return false;
+        }
+        if (!dicNode->isTerminalWordNode()) {
+            return false;
+        }
+        const int16_t pointIndex = dicNode->getInputIndex(0);
+        return pointIndex <= inputSize && !dicNode->isTotalInputSizeExceedingLimit()
+                && !dicNode->shouldBeFilterdBySafetyNetForBigram();
+    }
+
+    AK_FORCE_INLINE bool shouldDepthLevelCache(
+            const DicTraverseSession *const traverseSession) const {
+        const int inputSize = traverseSession->getInputSize();
+        return traverseSession->isCacheBorderForTyping(inputSize);
+    }
+
+    AK_FORCE_INLINE bool shouldNodeLevelCache(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
+        return false;
+    }
+
+    AK_FORCE_INLINE bool canDoLookAheadCorrection(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
+        const int inputSize = traverseSession->getInputSize();
+        return dicNode->canDoLookAheadCorrection(inputSize);
+    }
+
+    AK_FORCE_INLINE ProximityType getProximityType(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
+            const DicNode *const childDicNode) const {
+        return traverseSession->getProximityInfoState(0)->getProximityType(
+                dicNode->getInputIndex(0), childDicNode->getNodeCodePoint(),
+                true /* checkProximityChars */);
+    }
+
+    AK_FORCE_INLINE bool needsToTraverseAllUserInput() const {
+        return true;
+    }
+
+    AK_FORCE_INLINE float getMaxSpatialDistance() const {
+        return ScoringParams::MAX_SPATIAL_DISTANCE;
+    }
+
+    AK_FORCE_INLINE bool allowPartialCommit() const {
+        return true;
+    }
+
+    AK_FORCE_INLINE int getDefaultExpandDicNodeSize() const {
+        return DicNodeVector::DEFAULT_NODES_SIZE_FOR_OPTIMIZATION;
+    }
+
+    AK_FORCE_INLINE bool sameAsTyped(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
+        return traverseSession->getProximityInfoState(0)->sameAsTyped(
+                dicNode->getOutputWordBuf(), dicNode->getDepth());
+    }
+
+    AK_FORCE_INLINE int getMaxCacheSize() const {
+        return ScoringParams::MAX_CACHE_DIC_NODE_SIZE;
+    }
+
+    AK_FORCE_INLINE bool isPossibleOmissionChildNode(
+            const DicTraverseSession *const traverseSession, const DicNode *const parentDicNode,
+            const DicNode *const dicNode) const {
+        const ProximityType proximityType =
+                getProximityType(traverseSession, parentDicNode, dicNode);
+        if (!DicNodeUtils::isProximityChar(proximityType)) {
+            return false;
+        }
+        return true;
+    }
+
+    AK_FORCE_INLINE bool isGoodToTraverseNextWord(const DicNode *const dicNode) const {
+        const int probability = dicNode->getProbability();
+        if (probability < ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY) {
+            return false;
+        }
+        const int c = dicNode->getOutputWordBuf()[0];
+        const bool shortCappedWord = dicNode->getDepth()
+                < ScoringParams::THRESHOLD_SHORT_WORD_LENGTH && isAsciiUpper(c);
+        return !shortCappedWord
+                || probability >= ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY_FOR_CAPPED;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(TypingTraversal);
+    static const bool CORRECT_OMISSION;
+    static const bool CORRECT_SPACE_SUBSTITUTION;
+    static const bool CORRECT_SPACE_OMISSION;
+    static const TypingTraversal sInstance;
+
+    TypingTraversal() {}
+    ~TypingTraversal() {}
+};
+} // namespace latinime
+#endif // LATINIME_TYPING_TRAVERSAL_H
diff --git a/native/jni/src/suggest/typing_suggest.cpp b/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
similarity index 73%
copy from native/jni/src/suggest/typing_suggest.cpp
copy to native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
index 56bd5b6..1500341 100644
--- a/native/jni/src/suggest/typing_suggest.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-#include "typing_suggest.h"
+#include "suggest/policyimpl/typing/typing_weighting.h"
+
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/policyimpl/typing/scoring_params.h"
 
 namespace latinime {
-    SuggestInterface *(*TypingSuggest::sTypingSuggestFactoryMethod)() = 0;
-
-    TypingSuggest::~TypingSuggest() {
-        delete mSuggestInterface;
-    }
-} // namespace latinime
+const TypingWeighting TypingWeighting::sInstance;
+}  // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
new file mode 100644
index 0000000..52d54eb
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -0,0 +1,195 @@
+/*
+ * 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_TYPING_WEIGHTING_H
+#define LATINIME_TYPING_WEIGHTING_H
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/policy/weighting.h"
+#include "suggest/core/session/dic_traverse_session.h"
+#include "suggest/policyimpl/typing/scoring_params.h"
+
+namespace latinime {
+
+class DicNode;
+struct DicNode_InputStateG;
+
+class TypingWeighting : public Weighting {
+ public:
+    static const TypingWeighting *getInstance() { return &sInstance; }
+
+ protected:
+    float getTerminalSpatialCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
+        float cost = 0.0f;
+        if (dicNode->hasMultipleWords()) {
+            cost += ScoringParams::HAS_MULTI_WORD_TERMINAL_COST;
+        }
+        if (dicNode->getProximityCorrectionCount() > 0) {
+            cost += ScoringParams::HAS_PROXIMITY_TERMINAL_COST;
+        }
+        if (dicNode->getEditCorrectionCount() > 0) {
+            cost += ScoringParams::HAS_EDIT_CORRECTION_TERMINAL_COST;
+        }
+        return cost;
+    }
+
+    float getOmissionCost(const DicNode *const parentDicNode, const DicNode *const dicNode) const {
+        bool sameCodePoint = false;
+        bool isFirstLetterOmission = false;
+        float cost = 0.0f;
+        sameCodePoint = dicNode->isSameNodeCodePoint(parentDicNode);
+        // If the traversal omitted the first letter then the dicNode should now be on the second.
+        isFirstLetterOmission = dicNode->getDepth() == 2;
+        if (isFirstLetterOmission) {
+            cost = ScoringParams::OMISSION_COST_FIRST_CHAR;
+        } else {
+            cost = sameCodePoint ? ScoringParams::OMISSION_COST_SAME_CHAR
+                    : ScoringParams::OMISSION_COST;
+        }
+        return cost;
+    }
+
+    float getMatchedCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
+            DicNode_InputStateG *inputStateG) const {
+        const int pointIndex = dicNode->getInputIndex(0);
+        // Note: min() required since length can be MAX_POINT_TO_KEY_LENGTH for characters not on
+        // the keyboard (like accented letters)
+        const float length = min(ScoringParams::MAX_SPATIAL_DISTANCE,
+                traverseSession->getProximityInfoState(0)->getPointToKeyLength(
+                        pointIndex, dicNode->getNodeCodePoint()));
+        const float weightedDistance = length * ScoringParams::DISTANCE_WEIGHT_LENGTH;
+        const bool isFirstChar = pointIndex == 0;
+        const bool isProximity = isProximityDicNode(traverseSession, dicNode);
+        const float cost = isProximity ? (isFirstChar ? ScoringParams::FIRST_PROXIMITY_COST
+                : ScoringParams::PROXIMITY_COST) : 0.0f;
+        return weightedDistance + cost;
+    }
+
+    bool isProximityDicNode(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
+        const int pointIndex = dicNode->getInputIndex(0);
+        const int primaryCodePoint = toBaseLowerCase(
+                traverseSession->getProximityInfoState(0)->getPrimaryCodePointAt(pointIndex));
+        const int dicNodeChar = toBaseLowerCase(dicNode->getNodeCodePoint());
+        return primaryCodePoint != dicNodeChar;
+    }
+
+    float getTranspositionCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const parentDicNode,
+            const DicNode *const dicNode) const {
+        const int16_t parentPointIndex = parentDicNode->getInputIndex(0);
+        const int prevCodePoint = parentDicNode->getNodeCodePoint();
+        const float distance1 = traverseSession->getProximityInfoState(0)->getPointToKeyLength(
+                parentPointIndex + 1, prevCodePoint);
+        const int codePoint = dicNode->getNodeCodePoint();
+        const float distance2 = traverseSession->getProximityInfoState(0)->getPointToKeyLength(
+                parentPointIndex, codePoint);
+        const float distance = distance1 + distance2;
+        const float weightedLengthDistance =
+                distance * ScoringParams::DISTANCE_WEIGHT_LENGTH;
+        return ScoringParams::TRANSPOSITION_COST + weightedLengthDistance;
+    }
+
+    float getInsertionCost(
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const parentDicNode, const DicNode *const dicNode) const {
+        const int16_t parentPointIndex = parentDicNode->getInputIndex(0);
+        const int prevCodePoint =
+                traverseSession->getProximityInfoState(0)->getPrimaryCodePointAt(parentPointIndex);
+
+        const int currentCodePoint = dicNode->getNodeCodePoint();
+        const bool sameCodePoint = prevCodePoint == currentCodePoint;
+        const float dist = traverseSession->getProximityInfoState(0)->getPointToKeyLength(
+                parentPointIndex + 1, currentCodePoint);
+        const float weightedDistance = dist * ScoringParams::DISTANCE_WEIGHT_LENGTH;
+        const bool singleChar = dicNode->getDepth() == 1;
+        const float cost = (singleChar ? ScoringParams::INSERTION_COST_FIRST_CHAR : 0.0f)
+                + (sameCodePoint ? ScoringParams::INSERTION_COST_SAME_CHAR
+                        : ScoringParams::INSERTION_COST);
+        return cost + weightedDistance;
+    }
+
+    float getNewWordCost(const DicNode *const dicNode) const {
+        const bool isCapitalized = dicNode->isCapitalized();
+        return isCapitalized ?
+                ScoringParams::COST_NEW_WORD_CAPITALIZED : ScoringParams::COST_NEW_WORD;
+    }
+
+    float getNewWordBigramCost(
+            const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
+            hash_map_compat<int, int16_t> *const bigramCacheMap) const {
+        return DicNodeUtils::getBigramNodeImprobability(traverseSession->getOffsetDict(),
+                dicNode, bigramCacheMap);
+    }
+
+    float getCompletionCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const {
+        // The auto completion starts when the input index is same as the input size
+        const bool firstCompletion = dicNode->getInputIndex(0)
+                == traverseSession->getInputSize();
+        // TODO: Change the cost for the first completion for the gesture?
+        const float cost = firstCompletion ? ScoringParams::COST_FIRST_LOOKAHEAD
+                : ScoringParams::COST_LOOKAHEAD;
+        return cost;
+    }
+
+    float getTerminalLanguageCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode, const float dicNodeLanguageImprobability) const {
+        const bool hasEditCount = dicNode->getEditCorrectionCount() > 0;
+        const bool isSameLength = dicNode->getDepth() == traverseSession->getInputSize();
+        const bool hasMultipleWords = dicNode->hasMultipleWords();
+        const bool hasProximityErrors = dicNode->getProximityCorrectionCount() > 0;
+        // Gesture input is always assumed to have proximity errors
+        // because the input word shouldn't be treated as perfect
+        const bool isExactMatch = !hasEditCount && !hasMultipleWords
+                && !hasProximityErrors && isSameLength;
+
+        const float totalPrevWordsLanguageCost = dicNode->getTotalPrevWordsLanguageCost();
+        const float languageImprobability = isExactMatch ? 0.0f : dicNodeLanguageImprobability;
+        const float languageWeight = ScoringParams::DISTANCE_WEIGHT_LANGUAGE;
+        // TODO: Caveat: The following equation should be:
+        // totalPrevWordsLanguageCost + (languageImprobability * languageWeight);
+        return (totalPrevWordsLanguageCost + languageImprobability) * languageWeight;
+    }
+
+    AK_FORCE_INLINE bool needsToNormalizeCompoundDistance() const {
+        return false;
+    }
+
+    AK_FORCE_INLINE float getAdditionalProximityCost() const {
+        return ScoringParams::ADDITIONAL_PROXIMITY_COST;
+    }
+
+    AK_FORCE_INLINE float getSubstitutionCost() const {
+        return ScoringParams::SUBSTITUTION_COST;
+    }
+
+    AK_FORCE_INLINE float getSpaceSubstitutionCost() const {
+        return ScoringParams::SPACE_SUBSTITUTION_COST;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(TypingWeighting);
+    static const TypingWeighting sInstance;
+
+    TypingWeighting() {}
+    ~TypingWeighting() {}
+};
+} // namespace latinime
+#endif // LATINIME_TYPING_WEIGHTING_H
diff --git a/native/jni/src/suggest/typing_suggest.h b/native/jni/src/suggest/typing_suggest.h
deleted file mode 100644
index 678037a..0000000
--- a/native/jni/src/suggest/typing_suggest.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_TYPING_SUGGEST_H
-#define LATINIME_TYPING_SUGGEST_H
-
-#include "defines.h"
-#include "suggest_interface.h"
-
-namespace latinime {
-
-class ProximityInfo;
-
-class TypingSuggest : public SuggestInterface {
- public:
-    TypingSuggest() : mSuggestInterface(getTypingSuggestInstance()) {}
-
-    virtual ~TypingSuggest();
-
-    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) const {
-        if (!mSuggestInterface) {
-            return 0;
-        }
-        return mSuggestInterface->getSuggestions(pInfo, traverseSession, inputXs, inputYs, times,
-                pointerIds, inputCodePoints, inputSize, commitPoint, outWords, frequencies,
-                outputIndices, outputTypes);
-    }
-
-    static void setTypingSuggestFactoryMethod(SuggestInterface *(*factoryMethod)()) {
-        sTypingSuggestFactoryMethod = factoryMethod;
-    }
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(TypingSuggest);
-    static SuggestInterface *getTypingSuggestInstance() {
-        if (!sTypingSuggestFactoryMethod) {
-            return 0;
-        }
-        return sTypingSuggestFactoryMethod();
-    }
-
-    static SuggestInterface *(*sTypingSuggestFactoryMethod)();
-    SuggestInterface *mSuggestInterface;
-};
-} // namespace latinime
-#endif // LATINIME_TYPING_SUGGEST_H
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index 80ba412..a672294 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -22,6 +22,7 @@
 #include "char_utils.h"
 #include "defines.h"
 #include "dictionary.h"
+#include "digraph_utils.h"
 #include "proximity_info.h"
 #include "terminal_attributes.h"
 #include "unigram_dictionary.h"
@@ -30,19 +31,10 @@
 
 namespace latinime {
 
-const UnigramDictionary::digraph_t UnigramDictionary::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 UnigramDictionary::digraph_t UnigramDictionary::FRENCH_LIGATURES_DIGRAPHS[] =
-        { { 'a', 'e', 0x00E6 }, // U+00E6 : LATIN SMALL LETTER AE
-        { 'o', 'e', 0x0153 } }; // U+0153 : LATIN SMALL LIGATURE OE
-
 // TODO: check the header
-UnigramDictionary::UnigramDictionary(const uint8_t *const streamStart, const unsigned int flags)
+UnigramDictionary::UnigramDictionary(const uint8_t *const streamStart, const unsigned int dictFlags)
         : DICT_ROOT(streamStart), ROOT_POS(0),
-          MAX_DIGRAPH_SEARCH_DEPTH(DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH), FLAGS(flags) {
+          MAX_DIGRAPH_SEARCH_DEPTH(DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH), DICT_FLAGS(dictFlags) {
     if (DEBUG_DICT) {
         AKLOGI("UnigramDictionary - constructor");
     }
@@ -58,7 +50,7 @@
 
 // Return the replacement code point for a digraph, or 0 if none.
 int UnigramDictionary::getDigraphReplacement(const int *codes, const int i, const int inputSize,
-        const digraph_t *const digraphs, const unsigned int digraphsSize) const {
+        const DigraphUtils::digraph_t *const digraphs, const unsigned int digraphsSize) const {
 
     // There can't be a digraph if we don't have at least 2 characters to examine
     if (i + 2 > inputSize) return false;
@@ -74,7 +66,7 @@
 
     // It's an interesting digraph if the second char matches too.
     if (digraphs[lastDigraphIndex].second == codes[i + 1]) {
-        return digraphs[lastDigraphIndex].replacement;
+        return digraphs[lastDigraphIndex].compositeGlyph;
     } else {
         return 0;
     }
@@ -93,7 +85,7 @@
         const bool useFullEditDistance, const int *codesSrc,
         const int codesRemain, const int currentDepth, int *codesDest, Correction *correction,
         WordsPriorityQueuePool *queuePool,
-        const digraph_t *const digraphs, const unsigned int digraphsSize) const {
+        const DigraphUtils::digraph_t *const digraphs, const unsigned int digraphsSize) const {
     ASSERT(sizeof(codesDest[0]) == sizeof(codesSrc[0]));
     ASSERT(sizeof(xCoordinatesBuffer[0]) == sizeof(xcoordinates[0]));
     ASSERT(sizeof(yCoordinatesBuffer[0]) == sizeof(ycoordinates[0]));
@@ -169,7 +161,10 @@
     queuePool.clearAll();
     Correction masterCorrection;
     masterCorrection.resetCorrection();
-    if (BinaryFormat::REQUIRES_GERMAN_UMLAUT_PROCESSING & FLAGS)
+    const DigraphUtils::digraph_t *digraphs = 0;
+    const int digraphsSize =
+            DigraphUtils::getAllDigraphsForDictionaryAndReturnSize(DICT_FLAGS, &digraphs);
+    if (digraphsSize > 0)
     { // Incrementally tune the word and try all possibilities
         int codesBuffer[sizeof(*inputCodePoints) * inputSize];
         int xCoordinatesBuffer[inputSize];
@@ -177,15 +172,7 @@
         getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
                 xCoordinatesBuffer, yCoordinatesBuffer, inputSize, bigramMap, bigramFilter,
                 useFullEditDistance, inputCodePoints, inputSize, 0, codesBuffer, &masterCorrection,
-                &queuePool, GERMAN_UMLAUT_DIGRAPHS, NELEMS(GERMAN_UMLAUT_DIGRAPHS));
-    } else if (BinaryFormat::REQUIRES_FRENCH_LIGATURES_PROCESSING & FLAGS) {
-        int codesBuffer[sizeof(*inputCodePoints) * inputSize];
-        int xCoordinatesBuffer[inputSize];
-        int yCoordinatesBuffer[inputSize];
-        getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-                xCoordinatesBuffer, yCoordinatesBuffer, inputSize, bigramMap, bigramFilter,
-                useFullEditDistance, inputCodePoints, inputSize, 0, codesBuffer, &masterCorrection,
-                &queuePool, FRENCH_LIGATURES_DIGRAPHS, NELEMS(FRENCH_LIGATURES_DIGRAPHS));
+                &queuePool, digraphs, digraphsSize);
     } else { // Normal processing
         getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, inputCodePoints, inputSize,
                 bigramMap, bigramFilter, useFullEditDistance, &masterCorrection, &queuePool);
diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h
index c1955e8..a64a539 100644
--- a/native/jni/src/unigram_dictionary.h
+++ b/native/jni/src/unigram_dictionary.h
@@ -20,6 +20,7 @@
 #include <map>
 #include <stdint.h>
 #include "defines.h"
+#include "digraph_utils.h"
 
 namespace latinime {
 
@@ -29,8 +30,6 @@
 class WordsPriorityQueuePool;
 
 class UnigramDictionary {
-    typedef struct { int first; int second; int replacement; } digraph_t;
-
  public:
     // Error tolerances
     static const int DEFAULT_MAX_ERRORS = 2;
@@ -39,7 +38,7 @@
     static const int FLAG_MULTIPLE_SUGGEST_ABORT = 0;
     static const int FLAG_MULTIPLE_SUGGEST_SKIP = 1;
     static const int FLAG_MULTIPLE_SUGGEST_CONTINUE = 2;
-    UnigramDictionary(const uint8_t *const streamStart, const unsigned int flags);
+    UnigramDictionary(const uint8_t *const streamStart, const unsigned int dictFlags);
     int getProbability(const int *const inWord, const int length) const;
     int getBigramPosition(int pos, int *word, int offset, int length) const;
     int getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
@@ -47,6 +46,7 @@
             const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
             const bool useFullEditDistance, int *outWords, int *frequencies,
             int *outputTypes) const;
+    int getDictFlags() const { return DICT_FLAGS; }
     virtual ~UnigramDictionary();
 
  private:
@@ -57,13 +57,13 @@
             const bool useFullEditDistance, Correction *correction,
             WordsPriorityQueuePool *queuePool) const;
     int getDigraphReplacement(const int *codes, const int i, const int inputSize,
-            const digraph_t *const digraphs, const unsigned int digraphsSize) const;
+            const DigraphUtils::digraph_t *const digraphs, const unsigned int digraphsSize) const;
     void getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codesBuffer, int *xCoordinatesBuffer,
             int *yCoordinatesBuffer, const int codesBufferSize, const std::map<int, int> *bigramMap,
             const uint8_t *bigramFilter, const bool useFullEditDistance, const int *codesSrc,
             const int codesRemain, const int currentDepth, int *codesDest, Correction *correction,
-            WordsPriorityQueuePool *queuePool, const digraph_t *const digraphs,
+            WordsPriorityQueuePool *queuePool, const DigraphUtils::digraph_t *const digraphs,
             const unsigned int digraphsSize) const;
     void initSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codes, const int inputSize,
@@ -110,10 +110,7 @@
     const uint8_t *const DICT_ROOT;
     const int ROOT_POS;
     const int MAX_DIGRAPH_SEARCH_DEPTH;
-    const int FLAGS;
-
-    static const digraph_t GERMAN_UMLAUT_DIGRAPHS[];
-    static const digraph_t FRENCH_LIGATURES_DIGRAPHS[];
+    const int DICT_FLAGS;
 };
 } // namespace latinime
 #endif // LATINIME_UNIGRAM_DICTIONARY_H
diff --git a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
index 1398db9..850af94 100644
--- a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
@@ -113,7 +113,8 @@
             final String subtypeName = SubtypeLocale.getSubtypeDisplayName(subtype);
             final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
             final String spacebarText = MainKeyboardView.getShortDisplayName(subtype);
-            final String languageCode = StringUtils.toTitleCase(locale.getLanguage(), locale);
+            final String languageCode = StringUtils.capitalizeFirstCodePoint(
+                    locale.getLanguage(), locale);
             if (SubtypeLocale.isNoLanguage(subtype)) {
                 assertEquals(subtypeName, "", spacebarText);
             } else {
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/HermiteInterpolatorTests.java b/tests/src/com/android/inputmethod/keyboard/internal/HermiteInterpolatorTests.java
new file mode 100644
index 0000000..3ff5aa4
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/HermiteInterpolatorTests.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class HermiteInterpolatorTests extends AndroidTestCase {
+    private final HermiteInterpolator mInterpolator = new HermiteInterpolator();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    private static final float EPSLION = 0.0000005f;
+
+    private static void assertFloatEquals(final String message, float expected, float actual) {
+        if (Math.abs(expected - actual) >= EPSLION) {
+            fail(String.format("%s expected:<%s> but was:<%s>", message, expected, actual));
+        }
+    }
+
+    // t=0 p0=(0,1)
+    // t=1 p1=(1,0)
+    // t=2 p2=(3,2)
+    // t=3 p3=(2,3)
+    //   y
+    //   |
+    // 3 +       o p3
+    //   |
+    // 2 +           o p2
+    //   |
+    // 1 o p0
+    //   |    p1
+    // 0 +---o---+---+-- x
+    //   0   1   2   3
+    private final int[] mXCoords = { 0, 1, 3, 2 };
+    private final int[] mYCoords = { 1, 0, 2, 3 };
+    private static final int p0 = 0;
+    private static final int p1 = 1;
+    private static final int p2 = 2;
+    private static final int p3 = 3;
+
+    public void testP0P1() {
+        // [(p0 p1) p2 p3]
+        mInterpolator.reset(mXCoords, mYCoords, p0, p3 + 1);
+        mInterpolator.setInterval(p0 - 1, p0, p1, p1 + 1);
+        assertEquals("p0x", mXCoords[p0], mInterpolator.mP1X);
+        assertEquals("p0y", mYCoords[p0], mInterpolator.mP1Y);
+        assertEquals("p1x", mXCoords[p1], mInterpolator.mP2X);
+        assertEquals("p1y", mYCoords[p1], mInterpolator.mP2Y);
+        // XY-slope at p0=3.0 (-0.75/-0.25)
+        assertFloatEquals("slope x p0", -0.25f, mInterpolator.mSlope1X);
+        assertFloatEquals("slope y p0", -0.75f, mInterpolator.mSlope1Y);
+        // XY-slope at p1=1/3.0 (0.50/1.50)
+        assertFloatEquals("slope x p1",  1.50f, mInterpolator.mSlope2X);
+        assertFloatEquals("slope y p1",  0.50f, mInterpolator.mSlope2Y);
+        // t=0.0 (p0)
+        mInterpolator.interpolate(0.0f);
+        assertFloatEquals("t=0.0 x", 0.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.0 y", 1.0f, mInterpolator.mInterpolatedY);
+        // t=0.2
+        mInterpolator.interpolate(0.2f);
+        assertFloatEquals("t=0.2 x", 0.02400f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.2 y", 0.78400f, mInterpolator.mInterpolatedY);
+        // t=0.5
+        mInterpolator.interpolate(0.5f);
+        assertFloatEquals("t=0.5 x", 0.28125f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.5 y", 0.34375f, mInterpolator.mInterpolatedY);
+        // t=0.8
+        mInterpolator.interpolate(0.8f);
+        assertFloatEquals("t=0.8 x", 0.69600f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.8 y", 0.01600f, mInterpolator.mInterpolatedY);
+        // t=1.0 (p1)
+        mInterpolator.interpolate(1.0f);
+        assertFloatEquals("t=1.0 x", 1.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=1.0 y", 0.0f, mInterpolator.mInterpolatedY);
+    }
+
+    public void testP1P2() {
+        // [p0 (p1 p2) p3]
+        mInterpolator.reset(mXCoords, mYCoords, p0, p3 + 1);
+        mInterpolator.setInterval(p1 - 1, p1, p2, p2 + 1);
+        assertEquals("p1x", mXCoords[p1], mInterpolator.mP1X);
+        assertEquals("p1y", mYCoords[p1], mInterpolator.mP1Y);
+        assertEquals("p2x", mXCoords[p2], mInterpolator.mP2X);
+        assertEquals("p2y", mYCoords[p2], mInterpolator.mP2Y);
+        // XY-slope at p1=1/3.0 (0.50/1.50)
+        assertFloatEquals("slope x p1",  1.50f, mInterpolator.mSlope1X);
+        assertFloatEquals("slope y p1",  0.50f, mInterpolator.mSlope1Y);
+        // XY-slope at p2=3.0 (1.50/0.50)
+        assertFloatEquals("slope x p2",  0.50f, mInterpolator.mSlope2X);
+        assertFloatEquals("slope y p2",  1.50f, mInterpolator.mSlope2Y);
+        // t=0.0 (p1)
+        mInterpolator.interpolate(0.0f);
+        assertFloatEquals("t=0.0 x", 1.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.0 y", 0.0f, mInterpolator.mInterpolatedY);
+        // t=0.2
+        mInterpolator.interpolate(0.2f);
+        assertFloatEquals("t=0.2 x", 1.384f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.2 y", 0.224f, mInterpolator.mInterpolatedY);
+        // t=0.5
+        mInterpolator.interpolate(0.5f);
+        assertFloatEquals("t=0.5 x", 2.125f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.5 y", 0.875f, mInterpolator.mInterpolatedY);
+        // t=0.8
+        mInterpolator.interpolate(0.8f);
+        assertFloatEquals("t=0.8 x", 2.776f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.8 y", 1.616f, mInterpolator.mInterpolatedY);
+        // t=1.0 (p2)
+        mInterpolator.interpolate(1.0f);
+        assertFloatEquals("t=1.0 x", 3.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=1.0 y", 2.0f, mInterpolator.mInterpolatedY);
+    }
+
+    public void testP2P3() {
+        // [p0 p1 (p2 p3)]
+        mInterpolator.reset(mXCoords, mYCoords, p0, p3 + 1);
+        mInterpolator.setInterval(p2 - 1, p2, p3, p3 + 1);
+        assertEquals("p2x", mXCoords[p2], mInterpolator.mP1X);
+        assertEquals("p2y", mYCoords[p2], mInterpolator.mP1Y);
+        assertEquals("p3x", mXCoords[p3], mInterpolator.mP2X);
+        assertEquals("p3y", mYCoords[p3], mInterpolator.mP2Y);
+        // XY-slope at p2=3.0 (1.50/0.50)
+        assertFloatEquals("slope x p2",  0.50f, mInterpolator.mSlope1X);
+        assertFloatEquals("slope y p2",  1.50f, mInterpolator.mSlope1Y);
+        // XY-slope at p3=1/3.0 (-0.25/-0.75)
+        assertFloatEquals("slope x p3", -0.75f, mInterpolator.mSlope2X);
+        assertFloatEquals("slope y p3", -0.25f, mInterpolator.mSlope2Y);
+        // t=0.0 (p2)
+        mInterpolator.interpolate(0.0f);
+        assertFloatEquals("t=0.0 x", 3.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.0 y", 2.0f, mInterpolator.mInterpolatedY);
+        // t=0.2
+        mInterpolator.interpolate(0.2f);
+        assertFloatEquals("t=0.2 x", 2.98400f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.2 y", 2.30400f, mInterpolator.mInterpolatedY);
+        // t=0.5
+        mInterpolator.interpolate(0.5f);
+        assertFloatEquals("t=0.5 x", 2.65625f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.5 y", 2.71875f, mInterpolator.mInterpolatedY);
+        // t=0.8
+        mInterpolator.interpolate(0.8f);
+        assertFloatEquals("t=0.8 x", 2.21600f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.8 y", 2.97600f, mInterpolator.mInterpolatedY);
+        // t=1.0 (p3)
+        mInterpolator.interpolate(1.0f);
+        assertFloatEquals("t=1.0 x", 2.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=1.0 y", 3.0f, mInterpolator.mInterpolatedY);
+    }
+
+    public void testJustP1P2() {
+        // [(p1 p2)]
+        mInterpolator.reset(mXCoords, mYCoords, p1, p2 + 1);
+        mInterpolator.setInterval(p1 - 1, p1, p2, p2 + 1);
+        assertEquals("p1x", mXCoords[p1], mInterpolator.mP1X);
+        assertEquals("p1y", mYCoords[p1], mInterpolator.mP1Y);
+        assertEquals("p2x", mXCoords[p2], mInterpolator.mP2X);
+        assertEquals("p2y", mYCoords[p2], mInterpolator.mP2Y);
+        // XY-slope at p1=1.0 (2.0/2.0)
+        assertFloatEquals("slope x p1", 2.00f, mInterpolator.mSlope1X);
+        assertFloatEquals("slope y p1", 2.00f, mInterpolator.mSlope1Y);
+        // XY-slope at p2=1.0 (2.0/2.0)
+        assertFloatEquals("slope x p2", 2.00f, mInterpolator.mSlope2X);
+        assertFloatEquals("slope y p2", 2.00f, mInterpolator.mSlope2Y);
+        // t=0.0 (p1)
+        mInterpolator.interpolate(0.0f);
+        assertFloatEquals("t=0.0 x", 1.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.0 y", 0.0f, mInterpolator.mInterpolatedY);
+        // t=0.2
+        mInterpolator.interpolate(0.2f);
+        assertFloatEquals("t=0.2 x", 1.4f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.2 y", 0.4f, mInterpolator.mInterpolatedY);
+        // t=0.5
+        mInterpolator.interpolate(0.5f);
+        assertFloatEquals("t=0.5 x", 2.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.5 y", 1.0f, mInterpolator.mInterpolatedY);
+        // t=0.8
+        mInterpolator.interpolate(0.8f);
+        assertFloatEquals("t=0.8 x", 2.6f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=0.8 y", 1.6f, mInterpolator.mInterpolatedY);
+        // t=1.0 (p2)
+        mInterpolator.interpolate(1.0f);
+        assertFloatEquals("t=1.0 x", 3.0f, mInterpolator.mInterpolatedX);
+        assertFloatEquals("t=1.0 y", 2.0f, mInterpolator.mInterpolatedY);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index 4ccbf48..9e107a4 100644
--- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java
+++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
@@ -36,6 +36,7 @@
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import java.util.Locale;
 
@@ -130,7 +131,9 @@
     protected void setUp() throws Exception {
         super.setUp();
         mTextView = new MyTextView(getContext());
-        mTextView.setInputType(InputType.TYPE_CLASS_TEXT);
+        final int inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+                | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+        mTextView.setInputType(inputType);
         mTextView.setEnabled(true);
         setupService();
         mLatinIME = getService();
@@ -138,9 +141,7 @@
         mLatinIME.onCreate();
         setDebugMode(previousDebugSetting);
         final EditorInfo ei = new EditorInfo();
-        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
         final InputConnection ic = mTextView.onCreateInputConnection(ei);
-        ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
         final LayoutInflater inflater =
                 (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         final ViewGroup vg = new FrameLayout(getContext());
@@ -161,41 +162,22 @@
     // on the same thread that the tests are running on to mimic the actual environment as
     // closely as possible.
     // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method
-    // is called, so we need to do that at the right time so that #loop() returns at some
-    // point and we don't end up in an infinite loop.
-    // After we quit, the looper is still technically ready to process more messages but
-    // the handler will refuse to enqueue any because #quit() has been called and it
-    // explicitly tests for it on message enqueuing, so we'll have to reset it so that
-    // it lets us continue normal operation.
+    // is called, which has a lot of bad side effects. We can however just throw an exception
+    // in the runnable which will unwind the stack and allow us to exit.
+    private final class InterruptRunMessagesException extends RuntimeException {
+        // Empty class
+    }
     protected void runMessages() {
-        // Here begins deep magic.
-        final Looper looper = mLatinIME.mHandler.getLooper();
         mLatinIME.mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    looper.quit();
+                    throw new InterruptRunMessagesException();
                 }
             });
-        // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue).
-        // Once #quit() is called remaining messages are not processed, which is why we post
-        // a message that calls it instead of calling it directly.
-        Looper.loop();
-
-        // Once #quit() has been called, the message queue has an "mQuiting" field that prevents
-        // any subsequent post in this queue. However the queue itself is still fully functional!
-        // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal,
-        // coming back to this method to run the messages.
-        MessageQueue queue = Looper.myQueue();
         try {
-            // However there is no way of doing it externally, and mQuiting is private.
-            // So... get out the big guns.
-            java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting");
-            f.setAccessible(true); // What do you mean "private"?
-            f.setBoolean(queue, false);
-        } catch (NoSuchFieldException e) {
-            throw new RuntimeException(e);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException(e);
+            Looper.loop();
+        } catch (InterruptRunMessagesException e) {
+            // Resume normal operation
         }
     }
 
@@ -251,7 +233,8 @@
     }
 
     protected void pickSuggestionManually(final int index, final String suggestion) {
-        mLatinIME.pickSuggestionManually(index, suggestion);
+        mLatinIME.pickSuggestionManually(index, new SuggestedWordInfo(suggestion, 1,
+                SuggestedWordInfo.KIND_CORRECTION, "main"));
     }
 
     // Helper to avoid writing the try{}catch block each time
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
index 9e545a5..dc8837d 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
@@ -19,6 +19,7 @@
 import android.inputmethodservice.InputMethodService;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
@@ -141,11 +142,11 @@
         ic.beginBatchEdit();
         // basic case
         r = ic.getWordRangeAtCursor(" ", 0);
-        assertEquals("word", r.mWord);
+        assertTrue(TextUtils.equals("word", r.mWord));
 
         // more than one word
         r = ic.getWordRangeAtCursor(" ", 1);
-        assertEquals("word word", r.mWord);
+        assertTrue(TextUtils.equals("word word", r.mWord));
         ic.endBatchEdit();
 
         // tab character instead of space
@@ -153,28 +154,28 @@
         ic.beginBatchEdit();
         r = ic.getWordRangeAtCursor("\t", 1);
         ic.endBatchEdit();
-        assertEquals("word\tword", r.mWord);
+        assertTrue(TextUtils.equals("word\tword", r.mWord));
 
         // only one word doesn't go too far
         mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et));
         ic.beginBatchEdit();
         r = ic.getWordRangeAtCursor("\t", 1);
         ic.endBatchEdit();
-        assertEquals("word\tword", r.mWord);
+        assertTrue(TextUtils.equals("word\tword", r.mWord));
 
         // tab or space
         mockInputMethodService.setInputConnection(new MockConnection("one word\two", "rd", et));
         ic.beginBatchEdit();
         r = ic.getWordRangeAtCursor(" \t", 1);
         ic.endBatchEdit();
-        assertEquals("word\tword", r.mWord);
+        assertTrue(TextUtils.equals("word\tword", r.mWord));
 
         // tab or space multiword
         mockInputMethodService.setInputConnection(new MockConnection("one word\two", "rd", et));
         ic.beginBatchEdit();
         r = ic.getWordRangeAtCursor(" \t", 2);
         ic.endBatchEdit();
-        assertEquals("one word\tword", r.mWord);
+        assertTrue(TextUtils.equals("one word\tword", r.mWord));
 
         // splitting on supplementary character
         final String supplementaryChar = "\uD840\uDC8A";
@@ -183,6 +184,6 @@
         ic.beginBatchEdit();
         r = ic.getWordRangeAtCursor(supplementaryChar, 0);
         ic.endBatchEdit();
-        assertEquals("word", r.mWord);
+        assertTrue(TextUtils.equals("word", r.mWord));
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
index 923ab2e..136faff 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
@@ -19,6 +19,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.Locale;
+
 @SmallTest
 public class StringUtilsTests extends AndroidTestCase {
     public void testContainsInArray() {
@@ -90,4 +92,145 @@
         assertEquals("in 5 elements at position 2,4", "key1,key3,key5",
                 StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5"));
     }
+
+
+    public void testCapitalizeFirstCodePoint() {
+        assertEquals("SSaa",
+                StringUtils.capitalizeFirstCodePoint("ßaa", Locale.GERMAN));
+        assertEquals("Aßa",
+                StringUtils.capitalizeFirstCodePoint("aßa", Locale.GERMAN));
+        assertEquals("Iab",
+                StringUtils.capitalizeFirstCodePoint("iab", Locale.ENGLISH));
+        assertEquals("CAmElCaSe",
+                StringUtils.capitalizeFirstCodePoint("cAmElCaSe", Locale.ENGLISH));
+        assertEquals("İab",
+                StringUtils.capitalizeFirstCodePoint("iab", new Locale("tr")));
+        assertEquals("AİB",
+                StringUtils.capitalizeFirstCodePoint("AİB", new Locale("tr")));
+        assertEquals("A",
+                StringUtils.capitalizeFirstCodePoint("a", Locale.ENGLISH));
+        assertEquals("A",
+                StringUtils.capitalizeFirstCodePoint("A", Locale.ENGLISH));
+    }
+
+    public void testCapitalizeFirstAndDowncaseRest() {
+        assertEquals("SSaa",
+                StringUtils.capitalizeFirstAndDowncaseRest("ßaa", Locale.GERMAN));
+        assertEquals("Aßa",
+                StringUtils.capitalizeFirstAndDowncaseRest("aßa", Locale.GERMAN));
+        assertEquals("Iab",
+                StringUtils.capitalizeFirstAndDowncaseRest("iab", Locale.ENGLISH));
+        assertEquals("Camelcase",
+                StringUtils.capitalizeFirstAndDowncaseRest("cAmElCaSe", Locale.ENGLISH));
+        assertEquals("İab",
+                StringUtils.capitalizeFirstAndDowncaseRest("iab", new Locale("tr")));
+        assertEquals("Aib",
+                StringUtils.capitalizeFirstAndDowncaseRest("AİB", new Locale("tr")));
+        assertEquals("A",
+                StringUtils.capitalizeFirstAndDowncaseRest("a", Locale.ENGLISH));
+        assertEquals("A",
+                StringUtils.capitalizeFirstAndDowncaseRest("A", Locale.ENGLISH));
+    }
+
+    public void testGetCapitalizationType() {
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType("capitalize"));
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType("cApITalize"));
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType("capitalizE"));
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType("__c a piu$@tali56ze"));
+        assertEquals(StringUtils.CAPITALIZE_FIRST,
+                StringUtils.getCapitalizationType("A__c a piu$@tali56ze"));
+        assertEquals(StringUtils.CAPITALIZE_FIRST,
+                StringUtils.getCapitalizationType("Capitalize"));
+        assertEquals(StringUtils.CAPITALIZE_FIRST,
+                StringUtils.getCapitalizationType("     Capitalize"));
+        assertEquals(StringUtils.CAPITALIZE_ALL,
+                StringUtils.getCapitalizationType("CAPITALIZE"));
+        assertEquals(StringUtils.CAPITALIZE_ALL,
+                StringUtils.getCapitalizationType("  PI26LIE"));
+        assertEquals(StringUtils.CAPITALIZE_NONE,
+                StringUtils.getCapitalizationType(""));
+    }
+
+    public void testIsIdenticalAfterUpcaseIsIdenticalAfterDowncase() {
+        assertFalse(StringUtils.isIdenticalAfterUpcase("capitalize"));
+        assertTrue(StringUtils.isIdenticalAfterDowncase("capitalize"));
+        assertFalse(StringUtils.isIdenticalAfterUpcase("cApITalize"));
+        assertFalse(StringUtils.isIdenticalAfterDowncase("cApITalize"));
+        assertFalse(StringUtils.isIdenticalAfterUpcase("capitalizE"));
+        assertFalse(StringUtils.isIdenticalAfterDowncase("capitalizE"));
+        assertFalse(StringUtils.isIdenticalAfterUpcase("__c a piu$@tali56ze"));
+        assertTrue(StringUtils.isIdenticalAfterDowncase("__c a piu$@tali56ze"));
+        assertFalse(StringUtils.isIdenticalAfterUpcase("A__c a piu$@tali56ze"));
+        assertFalse(StringUtils.isIdenticalAfterDowncase("A__c a piu$@tali56ze"));
+        assertFalse(StringUtils.isIdenticalAfterUpcase("Capitalize"));
+        assertFalse(StringUtils.isIdenticalAfterDowncase("Capitalize"));
+        assertFalse(StringUtils.isIdenticalAfterUpcase("     Capitalize"));
+        assertFalse(StringUtils.isIdenticalAfterDowncase("     Capitalize"));
+        assertTrue(StringUtils.isIdenticalAfterUpcase("CAPITALIZE"));
+        assertFalse(StringUtils.isIdenticalAfterDowncase("CAPITALIZE"));
+        assertTrue(StringUtils.isIdenticalAfterUpcase("  PI26LIE"));
+        assertFalse(StringUtils.isIdenticalAfterDowncase("  PI26LIE"));
+        assertTrue(StringUtils.isIdenticalAfterUpcase(""));
+        assertTrue(StringUtils.isIdenticalAfterDowncase(""));
+    }
+
+    private void checkCapitalize(final String src, final String dst, final String separators,
+            final Locale locale) {
+        assertEquals(dst, StringUtils.capitalizeEachWord(src, separators, locale));
+        assert(src.equals(dst)
+                == StringUtils.isIdenticalAfterCapitalizeEachWord(src, separators));
+    }
+
+    public void testCapitalizeEachWord() {
+        checkCapitalize("", "", " ", Locale.ENGLISH);
+        checkCapitalize("test", "Test", " ", Locale.ENGLISH);
+        checkCapitalize("    test", "    Test", " ", Locale.ENGLISH);
+        checkCapitalize("Test", "Test", " ", Locale.ENGLISH);
+        checkCapitalize("    Test", "    Test", " ", Locale.ENGLISH);
+        checkCapitalize(".Test", ".test", " ", Locale.ENGLISH);
+        checkCapitalize(".Test", ".Test", " .", Locale.ENGLISH);
+        checkCapitalize(".Test", ".Test", ". ", Locale.ENGLISH);
+        checkCapitalize("test and retest", "Test And Retest", " .", Locale.ENGLISH);
+        checkCapitalize("Test and retest", "Test And Retest", " .", Locale.ENGLISH);
+        checkCapitalize("Test And Retest", "Test And Retest", " .", Locale.ENGLISH);
+        checkCapitalize("Test And.Retest  ", "Test And.Retest  ", " .", Locale.ENGLISH);
+        checkCapitalize("Test And.retest  ", "Test And.Retest  ", " .", Locale.ENGLISH);
+        checkCapitalize("Test And.retest  ", "Test And.retest  ", " ", Locale.ENGLISH);
+        checkCapitalize("Test And.Retest  ", "Test And.retest  ", " ", Locale.ENGLISH);
+        checkCapitalize("test and ietest", "Test And İetest", " .", new Locale("tr"));
+        checkCapitalize("test and ietest", "Test And Ietest", " .", Locale.ENGLISH);
+        checkCapitalize("Test&Retest", "Test&Retest", " \n.!?*()&", Locale.ENGLISH);
+        checkCapitalize("Test&retest", "Test&Retest", " \n.!?*()&", Locale.ENGLISH);
+        checkCapitalize("test&Retest", "Test&Retest", " \n.!?*()&", Locale.ENGLISH);
+        checkCapitalize("rest\nrecreation! And in the end...",
+                "Rest\nRecreation! And In The End...", " \n.!?*,();&", Locale.ENGLISH);
+        checkCapitalize("lorem ipsum dolor sit amet", "Lorem Ipsum Dolor Sit Amet",
+                " \n.,!?*()&;", Locale.ENGLISH);
+        checkCapitalize("Lorem!Ipsum (Dolor) Sit * Amet", "Lorem!Ipsum (Dolor) Sit * Amet",
+                " \n,.;!?*()&", Locale.ENGLISH);
+        checkCapitalize("Lorem!Ipsum (dolor) Sit * Amet", "Lorem!Ipsum (Dolor) Sit * Amet",
+                " \n,.;!?*()&", Locale.ENGLISH);
+    }
+
+    public void testContainsAny() {
+        assertFalse(StringUtils.containsAny("", " "));
+        assertFalse(StringUtils.containsAny("test and retest", ""));
+        assertTrue(StringUtils.containsAny("test and retest", "x3iq o"));
+        assertTrue(StringUtils.containsAny("test and retest", "x3iqo "));
+        assertTrue(StringUtils.containsAny("test and retest", " x3iqo"));
+        assertFalse(StringUtils.containsAny("test and retest", "x3iqo"));
+        assertTrue(StringUtils.containsAny("test and retest", "tse "));
+        assertTrue(StringUtils.containsAny("test and retest.", ".?()"));
+        assertFalse(StringUtils.containsAny("test and retest", ".?()"));
+        // Surrogate pair
+        assertTrue(StringUtils.containsAny("test and \uD861\uDED7 retest.", "\uD861\uDED7"));
+        // Ill-formed string
+        assertFalse(StringUtils.containsAny("test and \uD861 retest.", "\uD861\uDED7"));
+        // Ill-formed string
+        assertFalse(StringUtils.containsAny("test and \uDED7 retest.", "\uD861\uDED7"));
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
index ade0109..bd87292 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -72,15 +72,12 @@
     private static final FormatSpec.FormatOptions VERSION3_WITH_DYNAMIC_UPDATE =
             new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
 
-    private static final String[] CHARACTERS = {
-        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
-        "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
-    };
-
     public BinaryDictIOTests() {
         super();
 
-        final Random random = new Random(123456);
+        final long time = System.currentTimeMillis();
+        Log.e(TAG, "Testing dictionary: seed is " + time);
+        final Random random = new Random(time);
         sWords.clear();
         generateWords(MAX_UNIGRAMS, random);
 
@@ -132,13 +129,16 @@
     /**
      * Generates a random word.
      */
-    private String generateWord(final int value) {
-        final int lengthOfChars = CHARACTERS.length;
+    private String generateWord(final Random random) {
         StringBuilder builder = new StringBuilder("a");
-        long lvalue = Math.abs((long)value);
-        while (lvalue > 0) {
-            builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]);
-            lvalue /= lengthOfChars;
+        int count = random.nextInt() % 30; // Arbitrarily 30 chars max
+        while (count > 0) {
+            final long r = Math.abs(random.nextInt());
+            if (r < 0) continue;
+            // Don't insert 0~20, but insert any other code point.
+            // Code points are in the range 0~0x10FFFF.
+            builder.appendCodePoint((int)(20 + r % (0x10FFFF - 20)));
+            --count;
         }
         return builder.toString();
     }
@@ -146,7 +146,7 @@
     private void generateWords(final int number, final Random random) {
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
-            wordSet.add(generateWord(random.nextInt()));
+            wordSet.add(generateWord(random));
         }
         sWords.addAll(wordSet);
     }
@@ -555,7 +555,7 @@
         // Test a word that isn't contained within the dictionary.
         final Random random = new Random((int)System.currentTimeMillis());
         for (int i = 0; i < 1000; ++i) {
-            final String word = generateWord(random.nextInt());
+            final String word = generateWord(random);
             if (sWords.indexOf(word) != -1) continue;
             runGetTerminalPosition(buffer, word, i, false);
         }
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java
new file mode 100644
index 0000000..fe3781d
--- /dev/null
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.makedict;
+
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.Word;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Random;
+
+/**
+ * Unit tests for BinaryDictInputOutput.
+ */
+public class FusionDictionaryTest extends TestCase {
+    private static final ArrayList<String> sWords = new ArrayList<String>();
+    private static final int MAX_UNIGRAMS = 1000;
+
+    private void prepare(final long seed) {
+        System.out.println("Seed is " + seed);
+        final Random random = new Random(seed);
+        sWords.clear();
+        generateWords(MAX_UNIGRAMS, random);
+    }
+
+    /**
+     * Generates a random word.
+     */
+    private String generateWord(final Random random) {
+        StringBuilder builder = new StringBuilder("a");
+        int count = random.nextInt() % 30;
+        while (count > 0) {
+            final long r = Math.abs(random.nextInt());
+            if (r < 0) continue;
+            // Don't insert 0~20, but insert any other code point.
+            // Code points are in the range 0~0x10FFFF.
+            if (builder.length() < 7)
+                builder.appendCodePoint((int)(20 +r % (0x10FFFF - 20)));
+            --count;
+        }
+        if (builder.length() == 1) return generateWord(random);
+        return builder.toString();
+    }
+
+    private void generateWords(final int number, final Random random) {
+        while (sWords.size() < number) {
+            sWords.add(generateWord(random));
+        }
+    }
+
+    private void checkDictionary(final FusionDictionary dict, final ArrayList<String> words,
+            int limit) {
+        assertNotNull(dict);
+        for (final String word : words) {
+            if (--limit < 0) return;
+            final CharGroup cg = FusionDictionary.findWordInTree(dict.mRoot, word);
+            if (null == cg) {
+                System.out.println("word " + dumpWord(word));
+                dumpDict(dict);
+            }
+            assertNotNull(cg);
+        }
+    }
+
+    private String dumpWord(final String word) {
+        final StringBuilder sb = new StringBuilder("");
+        for (int i = 0; i < word.length(); i = word.offsetByCodePoints(i, 1)) {
+            sb.append(word.codePointAt(i));
+            sb.append(" ");
+        }
+        return sb.toString();
+    }
+
+    private void dumpDict(final FusionDictionary dict) {
+        for (Word w : dict) {
+            System.out.println("Word " + dumpWord(w.mWord));
+        }
+    }
+
+    // Test the flattened array contains the expected number of nodes, and
+    // that it does not contain any duplicates.
+    public void testFusion() {
+        final FusionDictionary dict = new FusionDictionary(new Node(),
+                new DictionaryOptions(new HashMap<String, String>(),
+                        false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */));
+        final long time = System.currentTimeMillis();
+        prepare(time);
+        for (int i = 0; i < sWords.size(); ++i) {
+            System.out.println("Adding in pos " + i + " : " + dumpWord(sWords.get(i)));
+            dict.add(sWords.get(i), 180, null, false);
+            dumpDict(dict);
+            checkDictionary(dict, sWords, i);
+        }
+    }
+}